Search code examples
firebaseflutterfirebase-authenticationflutter-provider

Can't update Flutter Firebase_Auth from version 0.15 to 0.18 while keeping the StreamProvider


I have a large Flutter app that is working perfectly...

Now, I am struggling how to update the Firebase firebase_auth: ^0.15.5+3 plugin that has Breaking changes (since summer 2020).

I absolutely need to keep my StreamProvider as I check MyUser in multiple locations of the app.

I re-created a minimal working app to focus on my specific problem.

Can you suggest how I should adjust these:

1- getCurrentUser in AuthService: It seems I need to use authStateChangesbut dont know how.

2- StreamProvider in MyApp: How to adjust this so I can keep using it in my app without changing anything.

3- Using this way: final currentUser = Provider.of<MyUser>(context) then currentUser.uidto get the uid String.

Please, be specific with concrete example.

Here is my puspec.yaml

name: myApp
environment:
  sdk: ">=2.7.0 <3.0.0"
dependencies:
  flutter:
    sdk: flutter

  # **** FIREBASE ****
  firebase_auth: ^0.15.5+3
  provider: ^4.0.5
  #firebase_core: "^0.5.2"
  #firebase_auth: "^0.18.3"

dev_dependencies:
  flutter_test:
    sdk: flutter
flutter:
  uses-material-design: true
  assets:
    - assets/

here is all the (working) code with previous Firebase version 0.15

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:firebase_auth/firebase_auth.dart';

void main() {
  runApp(MyApp());
}
//******************************************
class MyApp extends StatelessWidget  {
  @override Widget build(BuildContext context) {
    //The following StreamProvider is what I am trying to recreate with new Firebase version.
    return StreamProvider<MyUser>.value(
      value: AuthService().getCurrentUser,
      child: MaterialApp( title: 'MyApp',
        home: Wrapper(),),);}
}
//******************************************
class Wrapper extends StatelessWidget {
  @override  Widget build(BuildContext context) {
    final currentUser = Provider.of<MyUser>(context);

    // return either the Home or Authenticate widget
    if (currentUser == null) {return SignIn();}
    else {return HomePage();}}
}
//******************************************
class MyUser {
  String uid;
  MyUser({this.uid ,});
}
//******************************************
class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;
  //This method will need to be updated I guess
  Stream<MyUser> get getCurrentUser {
    return _auth.onAuthStateChanged.map((FirebaseUser user) => _refereeFromFirebaseUser(user));
  }
  MyUser _refereeFromFirebaseUser(FirebaseUser _authUser) {
    return (_authUser != null) ? MyUser(uid: _authUser.uid) : null;
  }

  Future signInWithEmailAndPassword(String email, String password) async {
    try {
      AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
      FirebaseUser user = result.user;
      return user;
    } catch (error) {
      print(error.toString());
      return null;}
  }

  Future signOut() async {
    try {return await _auth.signOut();}
    catch (error) {print(error.toString());return null;}
  }
}
//******************************************
class HomePage extends StatelessWidget {
  final AuthService _auth = AuthService();

  @override  Widget build(BuildContext context) {
    final currentUser = Provider.of<MyUser>(context);
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('MyApp2'),
          actions: <Widget>[FlatButton.icon(icon: Icon(Icons.person), label: Text('logout'), onPressed: () async {await _auth.signOut();},),  ],),
        //Following line is very important: this is what I'd like to replicate with new Firebase version
        body: Text("My ID is ${currentUser.uid}"),
      ),);
  }
}
//******************************************
class SignIn extends StatefulWidget {
  @override _SignInState createState() => _SignInState();
}

class _SignInState extends State<SignIn> {

  final AuthService _auth = AuthService();
  final _formKey = GlobalKey<FormState>();
  String error = '';
  String email ;
  String password ;

  @override Widget build(BuildContext context) {
    return Scaffold(
      body:  Form(key: _formKey,
        child: ListView(shrinkWrap: true, children: <Widget>[ SizedBox(height: 60.0),
          emailField(),     SizedBox(height: 8.0),
          passwordField(),  SizedBox(height: 24.0),
          signInButton(),
          Text(error, style: TextStyle(color: Colors.red, fontSize: 16.0),),
        ],),),);}

  RaisedButton signInButton()  {
    return RaisedButton( child: Text('Sign In'),
        onPressed: () async {
          if(_formKey.currentState.validate()) {
            dynamic result = await _auth.signInWithEmailAndPassword(email, password);
            //If successful SignIn, the Provider listening in the Wrapper will automatically load the Home page.
            if(result == null) {setState(() {error = 'Could not sign in with those credentials';});}}});
  }
  TextFormField passwordField() {
    return TextFormField(
      validator: (val) => val.length < 6 ? 'Enter a password 6+ chars long' : null,
      onChanged: (val) {setState(() => password = val);},);
  }
  TextFormField emailField() {
    return TextFormField(
      validator: (val) => val.isEmpty ? 'Enter an email' : null,
      onChanged: (val) {setState(() => email = val);},);
  }
}

Solution

  • I was able to migrate the whole code. It is now 100% running with new firebase_auth: "^0.18.3".

    (Big thanks to Net Ninja for his web course with the initial code)

    Here is new pubspec.yaml:

    name: flutter_app
    environment:
      sdk: ">=2.7.0 <3.0.0"
    dependencies:
      flutter:
        sdk: flutter
    
      # **** FIREBASE ****
      provider: ^4.0.5
      firebase_core: "^0.5.2"
      firebase_auth: "^0.18.3"
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
    flutter:
      uses-material-design: true
      assets:
        - assets/
    

    and here is the main.dart:

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:firebase_core/firebase_core.dart'; //New
    
    void main() {
      WidgetsFlutterBinding.ensureInitialized();//New
      runApp(Initialize());
    }
    //******************************************
    class Initialize extends StatelessWidget {//New
      final Future<FirebaseApp> _initialization = Firebase.initializeApp();
    
      @override Widget build(BuildContext context) {
        return FutureBuilder(
          future: _initialization,
          builder: (context, snapshot) {
            if (snapshot.hasError) {print("Error-01"); return myCircularProgress();} //to be updated
            if (snapshot.connectionState == ConnectionState.done) {return MyApp();}
            else {print("loading-02"); return myCircularProgress();}
          },);}
      Center myCircularProgress() => Center(child: SizedBox(child: CircularProgressIndicator(), height: 100.0, width: 100.0,));
    }
    //******************************************
    class MyApp extends StatelessWidget  {
      @override Widget build(BuildContext context) {
        return StreamProvider<MyUser>.value(
          value: AuthService().getCurrentUser,
          child: MaterialApp( title: 'MyApp',
            home: Wrapper(),),);}
    }
    //******************************************
    class Wrapper extends StatelessWidget {
      @override  Widget build(BuildContext context) {
        final currentUser = Provider.of<MyUser>(context);
    
        // return either the Home or Authenticate widget
        if (currentUser == null) {return SignIn();}
        else {return HomePage();}}
    }
    //******************************************
    class MyUser {
      String uid;
      MyUser({this.uid ,});
    }
    //******************************************
    class AuthService {
      final FirebaseAuth _auth = FirebaseAuth.instance;
      Stream<MyUser> get getCurrentUser {
        return _auth.authStateChanges().map((User user) => _refereeFromFirebaseUser(user)); // Also works with "_auth.idTokenChanges()" or "_auth.userChanges()"
      }
      MyUser _refereeFromFirebaseUser(User _authUser) {
        return (_authUser != null) ? MyUser(uid: _authUser.uid) : null;
      }
    
      Future signInWithEmailAndPassword(String email, String password) async {
        try {
          //AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
          UserCredential result = await _auth.signInWithEmailAndPassword(email: email, password: password);
          User user = result.user;
          return user;
        } catch (error) {
          print(error.toString());
          return null;}
      }
    
      Future signOut() async {
        try {return await _auth.signOut();}
        catch (error) {print(error.toString());return null;}
      }
    }
    //******************************************
    class HomePage extends StatelessWidget {
      final AuthService _auth = AuthService();
    
      @override  Widget build(BuildContext context) {
        final currentUser = Provider.of<MyUser>(context);
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: Text('MyApp v0.18'),
              actions: <Widget>[FlatButton.icon(icon: Icon(Icons.person), label: Text('logout'), onPressed: () async {await _auth.signOut();},),  ],),
           
            body: Text("My ID is ${currentUser.uid}"),
          ),);
      }
    }
    //******************************************
    class SignIn extends StatefulWidget {
      @override _SignInState createState() => _SignInState();
    }
    
    class _SignInState extends State<SignIn> {
    
      final AuthService _auth = AuthService();
      final _formKey = GlobalKey<FormState>();
      String error = '';
      String email ;
      String password ;
    
      @override Widget build(BuildContext context) {
        return Scaffold(
          body:  Form(key: _formKey,
            child: ListView(shrinkWrap: true, children: <Widget>[ SizedBox(height: 60.0),
              emailField(),     SizedBox(height: 8.0),
              passwordField(),  SizedBox(height: 24.0),
              signInButton(),
              Text(error, style: TextStyle(color: Colors.red, fontSize: 16.0),),
            ],),),);}
    
      RaisedButton signInButton()  {
        return RaisedButton( child: Text('Sign In'),
            onPressed: () async {
              if(_formKey.currentState.validate()) {
                dynamic result = await _auth.signInWithEmailAndPassword(email, password);
                //If successful SignIn, the Provider listening in the Wrapper will automatically load the Home page.
                if(result == null) {setState(() {error = 'Could not sign in with those credentials';});}}});
      }
      TextFormField passwordField() {
        return TextFormField(
          validator: (val) => val.length < 6 ? 'Enter a password 6+ chars long' : null,
          onChanged: (val) {setState(() => password = val);},);
      }
      TextFormField emailField() {
        return TextFormField(
          validator: (val) => val.isEmpty ? 'Enter an email' : null,
          onChanged: (val) {setState(() => email = val);},);
      }
    }