Web 中的 Flutter firebase 实时数据库

Flutter firebase realtime database in web

我正在尝试让我的 flutter 应用程序在浏览器中运行,这取决于 firebase_database。实际上并没有关于如何执行此操作的任何文档,但我根据 firebase_core 和 firebase_auth 文档做了一些假设:

我的应用程序正在 iOS 和 android 上运行,但我无法让数据库在 flutter web 中运行。

我已经设置了 index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Flutter WebRTC Demo</title>
</head>
<body>
    <script src="https://www.gstatic.com/firebasejs/7.6.1/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/7.6.1/firebase-database.js"></script>
    <script>
        const firebaseConfig = {
            apiKey: '...',
            authDomain: '...',
            databaseURL: '...',
            projectId: '...',
            storageBucket: '...',
            messagingSenderId: '...',
            appId: '...'
        };
        firebase.initializeApp(firebaseConfig);
    </script>
    <script src="main.dart.js" type="application/javascript"></script>
</body>
</html>

但是,当我尝试使用 firebase 数据库时,我在日志中收到错误消息:

MissingPluginException(No implementation found for method DatabaseReference#set on channel plugins.flutter.io/firebase_database)
package:dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 196:49  throw_
package:flutter/src/services/platform_channel.dart 319:7                              invokeMethod
package:dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 47:50            onValue
package:dart-sdk/lib/async/zone.dart 1381:54                                          runUnary
package:dart-sdk/lib/async/future_impl.dart 139:18                                    handleValue
package:dart-sdk/lib/async/future_impl.dart 680:44                                    handleValueCallback
package:dart-sdk/lib/async/future_impl.dart 709:32                                    _propagateToListeners
package:dart-sdk/lib/async/future_impl.dart 524:5                                     [_completeWithValue]
package:dart-sdk/lib/async/future_impl.dart 554:7                                     callback
package:dart-sdk/lib/async/schedule_microtask.dart 43:11                              _microtaskLoop
package:dart-sdk/lib/async/schedule_microtask.dart 52:5                               _startMicrotaskLoop
package:dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 168:15           <fn>

有什么方法可以让实时数据库在我的网络版 Flutter 应用程序中运行?

FlutterFire 插件最初是为 iOS 和 Android 的原生移动应用程序而构建的。目前正在将对 Web 平台的支持添加到插件中,但要涵盖所有 Firebase 产品还需要一些时间。

您可以在 Github 存储库的 available FlutterFire plugins 列表中检查当前哪些模块与 Flutter for web 兼容。

要在 Flutter for web 的其他功能上使用 Firebase,请使用 firebase-dart plugin。这意味着您需要为 Web 和移动设备编写单独的代码,但您可以仅在应用的一小部分中隔离差异。

2021 年更新 现在支持对 firebase 数据库的 Web 支持。参见 PR here.


the main README in the flutterfire github中,有一个“Web?”用于记录哪些插件已准备好用于网络的列。

目前,Flutter Web 应用仅支持 firebase_corefirebase_authcloud_firestorefirebase_functions

正如@Frank van Puffelen 提到的,要在 flutter web 中使用 firebase 的全部功能,请使用 firebase-dart 包装器库。

还有一个 Flutter Web Plugins Project Board 显示路线图上有哪些 flutter 插件,以及它们处于哪个开发阶段。在本次编辑时,firebase_storage 是网络路线图上的下一个插件。

pre flutter 2.0解决方案。

在这个问题上经过几天的努力,并且由于评论中几乎没有未解决的问题,我决定 post 一个完整的、长长的答案,以帮助像我一样刚开始的人。 这就是我实现这两个不同包的方式。 当我使用 flutter_bloc 进行状态管理时,我基本上必须像对用户位置所做的那样使存储库平台依赖。 为了实现它,我使用了 stub/abstract class/ web implementation/ device implementation 模式。因此,在我集团的存储库中,我只是调用抽象 class 方法,这些方法将使用适当的包映射到适当的平台实现 class。一开始看起来有点乱,但一旦掌握了这个概念就很容易了,但是 Thera 是一些在开始使用模式时可能会掉入的陷阱。 对于设备实现,使用 flutter_auth 包,而对于 Web 实现,使用 flutter 包,为了简单起见,我制作了一个单例。现在,单例 return 是初始化的 firebase App,可让您访问所有服务.. auth()database()、firestore()`、remoteconfig()... 无论您需要访问任何 firebase 服务,只需实例化 Firebase 并使用这些服务即可。

App firebase = FirebaseWeb.instance.app;

...

await firebase.auth().signInWithCredential(credential);
    return firebase.auth().currentUser;

下面是我用于授权的所有代码,但很容易适应不同的 firebase 服务:

存根:

这只是为了持有一个 (getter) 方法,该方法在抽象 class 工厂方法(我称之为切换器)中得到 returned,并允许有条件的导入抽象 class 到正确的实现 class.

import 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_switcher.dart';

UserRepositorySwitcher getUserRepository() {
  print('user_repository_stub called');
}

摘要class(切换台):

在这里导入存根,以便能够有条件地导入正确的实现 class。 return 中的存根 (getter) 方法在 class 工厂方法中编辑。 在这个class中你需要声明所有你需要使用的方法。这里 returns 是动态的,因为包特定 returns 将在平台实现中 classes。 注意条件导入中的拼写错误和正确的文件路径,因为没有自动检查。我花了很多时间才找到它哈哈。。

import 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_stub.dart'
    if (dart.library.io) 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_device.dart'
    if (dart.library.js) 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_web.dart';

abstract class UserRepositorySwitcher {
  Future<dynamic> signInWithGoogle() async {
    print('UserREpository switcher signInWithGoogle() called');
  }

  Future<void> signInWithCredential({String email, String password}) {}
  Future<void> signUp({String email, String password}) {}
  Future<void> signOut() async {}
  Future<bool> isSignedIn() async {}
  Future<dynamic> getUser() async {}

  factory UserRepositorySwitcher() => getUserRepository();
}

设备实现class:

必须实现抽象 class 才能掌握并实现具有特定(flutter_auth 在本例中)方法和类型的方法。在这里,您还必须在 class 范围之外声明存根中的相同方法,即 return 的设备实现 class(参见底部代码)。

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebaseblocwebstub/authentication_bloc/app_user.dart';
import 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_switcher.dart';
import 'package:google_sign_in/google_sign_in.dart';

class UserRepositoryDevice implements UserRepositorySwitcher {
  final FirebaseAuth _firebaseAuth;
  final GoogleSignIn _googleSignIn;

  UserRepositoryDevice({FirebaseAuth firebaseAuth, GoogleSignIn googleSignIn})
      : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance,
        _googleSignIn = googleSignIn ?? GoogleSignIn();

  Future<FirebaseUser> signInWithGoogle() async {
    print('signInWithGoogle() from device started');
    final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
    print('GoogleUser is : $googleUser');
    final GoogleSignInAuthentication googleAuth =
        await googleUser.authentication;
    final AuthCredential credential = await GoogleAuthProvider.getCredential(
        idToken: googleAuth.idToken, accessToken: googleAuth.accessToken);
    await _firebaseAuth.signInWithCredential(credential);
    return _firebaseAuth.currentUser();
  }

  Future<void> signInWithCredential({String email, String password}) {
    return _firebaseAuth.signInWithEmailAndPassword(
        email: email, password: password);
  }

  Future<void> signUp({String email, String password}) {
    return _firebaseAuth.createUserWithEmailAndPassword(
        email: email, password: password);
  }

  Future<void> signOut() async {
    return Future.wait([
      _firebaseAuth.signOut(),
      _googleSignIn.signOut(),
    ]);
  }

  Future<bool> isSignedIn() async {
    final currentUser = _firebaseAuth.currentUser();
    return currentUser != null;
  }

  Future<FixitUser> getUser() async {
    String displayName = (await _firebaseAuth.currentUser()).displayName;
    String email = (await _firebaseAuth.currentUser()).email;
    String uid = (await _firebaseAuth.currentUser()).uid;
    String photoUrl = (await _firebaseAuth.currentUser()).photoUrl;
    String phoneNumber = (await _firebaseAuth.currentUser()).phoneNumber;
    FixitUser user = FixitUser(
        // fixitUser
        name: displayName ?? '',
        email: email,
        phoneNumber: phoneNumber ?? '',
        uid: uid,
        photoUrl: photoUrl ?? '');
    return (user);
  }
}

UserRepositorySwitcher getUserRepository() => UserRepositoryDevice();

现在终于可以上网了..

firebase 单例:

为了方便地使用 firebase 包,我决定将其设为单例。 在这里你可以 return 一个 Future<App> 实例但是你必须 .then 一切..或者 return 直接 App..我选择了这种方式..更清洁和更快的实施。 这样你就不必在你的 index.html 文件中初始化 firebase,否则你会得到一个错误,因为它已经初始化了。在这里初始化 firebase 也让你的密钥不暴露..


import 'dart:async';
import 'package:firebase/firebase.dart';

class FirebaseWeb {
  // Singleton instance
  static final FirebaseWeb _singleton = FirebaseWeb._();

  // Singleton accessor
  static FirebaseWeb get instance => _singleton;

  // A private constructor. Allows us to create instances of AppDatabase
  // only from within the AppDatabase class itself.
  FirebaseWeb._();

  static App _app;
  // Database object accessor

  App get app {
    print('firebase get app called ');
    print('_app is $_app');
    if (_app != null) {
      return _app;
    } else {
      print('initialize app');
      _app = initializeApp(
          apiKey: "your key",
          authDomain: "your key",
          databaseURL: "your key",
          projectId: "your key",
          storageBucket: "your key",
          messagingSenderId: "your key",
          appId: "your key");
      print('initialized app is $_app'); // await _initializeApp();
      return _app;
    }
  }
}

网络实施:

在这里你只是使用单例实例化Firebase,并实现抽象class方法,使用它的服务和方法..我在这里使用auth()。 您可以看到(注释掉的部分)如果 return a Future<App> 在单身人士中,实施会更加冗长.. 这里存根 getter 方法将 return 这个 class ..(检查底部)

import 'dart:async';
import 'package:firebase/firebase.dart';
import 'package:firebaseblocwebstub/authentication_bloc/app_user.dart';
import 'package:firebaseblocwebstub/firebase_singleton.dart';
import 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_switcher.dart';
import 'package:google_sign_in/google_sign_in.dart';

class UserRepositoryWeb implements UserRepositorySwitcher {
  App firebase = FirebaseWeb.instance.app;
  final GoogleSignIn _googleSignIn = GoogleSignIn();

  Future<User> signInWithGoogle() async {
    print('signInWithGoogle() started');
    final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
    print('GoogleUser is : $googleUser');
    final GoogleSignInAuthentication googleAuth =
        await googleUser.authentication;
    final OAuthCredential credential = await GoogleAuthProvider.credential(
        googleAuth.idToken, googleAuth.accessToken);
    // singleton retunrning Future<App>
//    await firebase.then((firebase) {
//      firebase.auth().signInWithCredential(credential);
//      return;
//    });
//    return firebase.then((firebase) {
//      return firebase.auth().currentUser;
//    });

    await firebase.auth().signInWithCredential(credential);
    return firebase.auth().currentUser;
  }

  Future<void> signInWithCredential({String email, String password}) {
    return firebase.auth().signInWithEmailAndPassword(email, password);
    // singleton retunrning Future<App>
//    return firebase.then((firebase) {
//      return firebase.auth().signInWithEmailAndPassword(email, password);
//    });
  }

  Future<void> signUp({String email, String password}) {
    return firebase.auth().createUserWithEmailAndPassword(email, password);
    // singleton retunrning Future<App>
//    return firebase.then((firebase) {
//      return firebase.auth().createUserWithEmailAndPassword(email, password);
//    });
  }

  Future<void> signOut() async {
    return Future.wait([
      firebase.auth().signOut(),
// singleton retunrning Future<App>
//      firebase.then((firebase) {
//        firebase.auth().signOut();
//      }),
      _googleSignIn.signOut(),
    ]);
  }

  Future<bool> isSignedIn() async {
    final currentUser = firebase.auth().currentUser;
    return currentUser != null;
    // singleton retunrning Future<App>
//    User firebaseUser = firebase.then((firebase) {
//      return firebase.auth().currentUser;
//    }) as User;
//    return firebaseUser != null;
  }

  Future<FixitUser> getUser() async {
    // singleton retunrning Future<App>
//    User firebaseUser = firebase.then((firebase) {
//      return firebase.auth().currentUser;
//    }) as User;
//
//    FixitUser user = FixitUser(
//        name: firebaseUser.displayName ?? '',
//        email: firebaseUser.email,
//        phoneNumber: firebaseUser.phoneNumber ?? '',
//        uid: firebaseUser.uid,
//        photoUrl: firebaseUser.photoURL ?? '');
//    return (user);
//  }
    String displayName = (firebase.auth().currentUser).displayName;
    String email = (firebase.auth().currentUser).email;
    String uid = (firebase.auth().currentUser).uid;
    String photoUrl = (firebase.auth().currentUser).photoURL;
    String phoneNumber = (firebase.auth().currentUser).phoneNumber;

    FixitUser user = FixitUser(
        name: displayName ?? '',
        email: email,
        phoneNumber: phoneNumber ?? '',
        uid: uid,
        photoUrl: photoUrl ?? '');
    return (user);
  }
}

UserRepositorySwitcher getUserRepository() => UserRepositoryWeb();

我遇到了同样的问题,并决定采取一些措施。所以我继续做了 https://pub.dev/packages/firebase_db_web_unofficial 。它很容易设置并集成到您的代码中。

万一有人还在寻找 flutter web 实时数据库问题的另一种解决方法,我有一个简单而直接的解决方案...

我做了一些挖掘,if(kIsWeb) 似乎有效。

第一

为 android|ios 添加 firebase package that supports Realtime database for web and firebase_databe package

第二

初始化 firebase

void main() async {
await Firebase.initializeApp();
}

第三

导入如下

import 'package:firebase_database/firebase_database.dart';
import 'package:firebase/firebase.dart' as fb;

第四

关于如何读取 android-ios/web 的实时数据库数据的示例。 在这里,我将图像添加到轮播滑块。

List<SliderImage> sliderList = [];

void getSliderData() async {
FirebaseDatabase firebaseDatabaseference = FirebaseDatabase.instance;
firebaseDatabaseference.setPersistenceEnabled(true);
firebaseDatabaseference.setPersistenceCacheSizeBytes(10000000);



//for web
if (kIsWeb) {
  fb.DatabaseReference databaseRef = fb.database().ref("Slider");
  await databaseRef.onValue.listen((event) {
    fb.DataSnapshot dataSnapshot = event.snapshot;
    sliderList.clear();
    this.setState(() {
      for (var value in dataSnapshot.val()) {
        sliderList.add(new SliderImage.fromJson(value));
      }
    });
  });
  // for android and ios
} else {
  DatabaseReference databaseReference = firebaseDatabaseference.reference();

  databaseReference.keepSynced(true);
  await databaseReference
      .child("Slider")
      .once()
      .then((DataSnapshot dataSnapshot) {
    sliderList.clear();
    this.setState(() {
      for (var value in dataSnapshot.value) {
        sliderList.add(new SliderImage.fromJson(value));
      }
    });
  });
}

}

轮播滑块

CarouselSlider.builder(
      itemCount: sliderList.length,
      options: CarouselOptions(
        autoPlay: true,
        aspectRatio: 16 / 9,
        viewportFraction: 1,
        enlargeCenterPage: false,
        enlargeStrategy: CenterPageEnlargeStrategy.height,
      ),
      itemBuilder: (context, index, realIdx) {
        return Container(
          child: Center(
              child: Image.network(sliderList[index].image, loadingBuilder:
                  (BuildContext context, Widget child,
                      ImageChunkEvent loadingProgress) {
            if (loadingProgress == null) return child;
            return Center(
              child: CircularProgressIndicator(
                valueColor:
                    new AlwaysStoppedAnimation<Color>(Colors.black54),
                value: loadingProgress.expectedTotalBytes != null
                    ? loadingProgress.cumulativeBytesLoaded /
                        loadingProgress.expectedTotalBytes
                    : null,
              ),
            );
          }, fit: BoxFit.cover, width: 1000)),
        );
      },
    ));

SliderImage 模型class

class SliderImage {
 String image;

 SliderImage(this.image);

 SliderImage.fromJson(var value) {
   this.image = value['image'];
 }
}

类似的方法可以应用于 Listview.builder。 干杯

存在一个包https://pub.dev/packages/firebase_db_web_unofficial/install(非官方 Firebase 数据库集成) 这对我有用。以下是底部的 index.html 脚本(我也必须从 firebasedbunofficial 插入脚本),如 index.html

所示
index.html bottom script
<!-- The core Firebase JS SDK is always required and must be listed first -->

<script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-app.js"></script>

<script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-auth.js"></script>

<script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-database.js"></script>
<script src="https://api.daytonsquareroots.org/firebasedatabaseweb/v0.0.2/app.js" defer></script>
<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->

<!-- Initialize Firebase -->
<script>
  var firebaseConfig = {
        apiKey: "...",
        authDomain: "...",
        databaseURL: "...",
        projected: "...",
        storageBucket: "...",
        messagingSenderId: "...",
        appId: "...",
        measurementId: "G-...",
      };

      // Initialize Firebase
      firebase.initializeApp(firebaseConfig);
</script>
</body>
</html>

在 flutter 方面,您需要添加以下包。

flutter pub add firebase_db_web_unofficial

并执行以下代码

.
.
.
.
import 'package:firebase_db_web_unofficial/firebasedbwebunofficial.dart';

.
.
.

FirebaseApp app = await Firebase.initializeApp();
.
.
.

FirebaseDatabaseWeb.instance
                  .reference()
                  .child("Users")
                  .child("userid")
                  .set({
                      "name": "shahid",
                      "email": "shahid@gmail.com",
                      "password": "this will work"
                    });
.
.
.
.
.

今天刚刚合并到 master 中:

https://github.com/FirebaseExtended/flutterfire/pull/6952

好消息!官方包 firebase_database 现在原生支持 flutter web,我们不需要再经历这些麻烦了:D