简体   繁体   中英

Flutter firebase realtime database in web

I'm trying to get my flutter app working in the browser and it depends on firebase_database. There is not really any documentation for how to do this, but I'm making some assumptions based off the firebase_core and firebase_auth documentations:

My app is working on iOS and android, but I'm having trouble getting the database working in flutter web.

I've set up my 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>

But, when I try to use the firebase database, I get errors in the logs:

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>

Is there any way I can get the realtime database working in my flutter app on the web?

In the main README in the flutterfire github , there is a "Web?" column that to note which plugins are ready for web.

Currently, only firebase_core , firebase_auth , cloud_firestore , and firebase_functions are supported in flutter web apps.

As @Frank van Puffelen mentioned, to use the full functionality of firebase in flutter web, use the firebase-dart wrapper library.

There is also a Flutter Web Plugins Project Board that shows which flutter plugins are on the roadmap, and what stage of development they are at. At the time of this edit, firebase_storage is the next plugin on the web roadmap.

The FlutterFire plugins were originally built to work in native mobile apps for iOS and Android. Support for the web platform is being added to the plugins as we speak, but it will take some time before all Firebase products are covered.

You can check which modules are currently compatible with Flutter for web in this list of available FlutterFire plugins on the Github repo.

To use Firebase in Flutter for the web on other features, use the firebase-dart plugin . This means that you'll need separate code for web and for mobile, but you may be able to isolate the differences in just a small part of your app.

I had the same problem and decided to do something about it. So I went ahead and made https://pub.dev/packages/firebase_db_web_unofficial . It's easy to set up and integrate into your code.

Pre flutter 2.0 solution.

After a few days of struggle on this subject and as there are few unanswered questions in comments I decide to post a complete, dough long , answer to help people just starting out in flutter as I am. This is how I implement the two different packages. As I use flutter_bloc for state management I basically had to make the repository platform dependent as I did for user location. To achieve it I use a stub/abstract class/ web implementation/ device implementation pattern. So in my bloc's repository I just call the abstract class methods that will map to the proper platform implementation class using the appropriate package. It seems a bit messy at first but it's quite easy once grasped the concept but Thera are a couple of trap one could fall into when starting out with the pattern. For the device implementation flutter_auth package is used while for web implementation flutter package is used instead and to make it easy I made a singleton. Now the singleton returns the initialized firebase App that give you access to all services.. auth() , database() , firestore()`, remoteconfig()... Wherever you need to access any firebase service just instantiate Firebase and use the services.

App firebase = FirebaseWeb.instance.app;

...

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

So here is all the code I use for authorization, but is easy to adapt for different firebase services:

Stub:

this is just to hold a (getter) method that gets returned in the abstract class factory method (I call it switcher), and to allow for conditional import in abstract class to the proper implementation class.

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

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

Abstract class ( the switcher ) :

Here you import the stub to be able to conditionally import the proper implementation class. The stub (getter) method in returned in the class factory method. In this class you need to declare all the methods you need to use. Here returns are dynamic as the package specific returns will be in the platform implementation classes. Watch out for typos and proper file routes in the conditional import as there is no automatic check..costed me many ours to find it out hahah..

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();
}

Device implementation class:

Has to implement abstract class to get hold of and implement it's methods with specific ( flutter_auth in this case) methods and types. Here you also have to declare, outside the class scope, the same method in the stub, that returns device implementation class (see bottom code).

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();

Now finally for web..

firebase singleton:

To use firebase package in an easy way I decided to make it a singleton. Here you can either return a Future<App> instance but then you have to .then everything..or return the App directly..I chosen this way..cleaner and quicker implementation. This way you don't have to initialize firebase in your index.html file or you'll get an error as it's already initialized. Initialize firebase here also makes your keys not exposed..


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;
    }
  }
}

Web implementation:

Here you just instantiate Firebase using the singleton, and implement abstract class methods, use its services and methods..I use auth() here. You can see (commented out parts) how much more verbose is implementation if return a Future<App> in the singleton.. Here the stub getter method will return this class ..(check bottom)

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();

Just incase someone is still looking for another workaround for flutter web Realtime database issue, I have a simple and pretty straight forward solution...

I did some digging and if(kIsWeb) seems to work.

First

add the firebase package that supports Realtime database for web and firebase_databe package for android|ios.

Second

initialize firebase

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

Third

import as follows

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

Fourth

An example on how to read Realtime database data for android-ios / web. Here I am adding images to a carousel slider.

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));
      }
    });
  });
}

}

The carousel slider

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 model class

class SliderImage {
 String image;

 SliderImage(this.image);

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

Similar approach can be applied to Listview.builder. cheers 😀😀😀😀😀😀😀😀

Good news! The official package firebase_database now supports flutter web natively and we don't need to go through this hazzle anymore :D

A package exists https://pub.dev/packages/firebase_db_web_unofficial/install (Unofficial Firebase database integration) This worked for me. Following is my index.html script at the bottom (I had to insert script from firebasedbunofficial too ) as showing in 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>

On flutter side, you'll need to add following package.

flutter pub add firebase_db_web_unofficial

And execute the following code

.
.
.
.
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"
                    });
.
.
.
.
.

This just got merged into master today:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM