Eric Kemmer
Eric Kemmer

Reputation: 23

Unable to use Firebase-Auth and Provider to provide custom user class throughout my widget tree

My goal:

A custom defined User class that is available throughout my Flutter app and updates according to new data in Firebase.

How I tried to accomplish my goal:

  1. Obtain uid: Using FirebaseAuth I can successfully authenticate a user and obtain a uid (user id). I then check to see if that uid is in my Firebase database.

  2. Fetch or write to Firebase:

    • If the uid matches a user in Firebase I fetch that user's data
    • else I write to Firebase creating a new custom User class
  3. Instantiate my custom User class

  4. Use Provider to provide my Custom User class throughout my app

Issues I encountered:

  1. Code for Provider and Firebase must be separated leading to complexity in my code
    • Provider: requires a BuildContext which cannot be passed into an async function
    • Firebase: reads/writes to Firebase require an async function
  2. My providers do not appear to be called in my main.dart file therefore no read/write to Firebase occurs

side note: I don't think this is super relevant but I am using jsonserializable for my classes in my models.dart file

main.dart

import 'package:dev_odyssey/routes.dart';
import 'package:dev_odyssey/shared/shared.dart';
import 'package:dev_odyssey/theme.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import "package:flutter/material.dart";
import 'package:dev_odyssey/services/services.dart';
import 'package:provider/provider.dart';

void main() async {
  runApp(const App());
}

class App extends StatefulWidget {
  const App({super.key});

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  final Future<FirebaseApp> _initialization = Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FutureBuilder(
        future: _initialization,
        builder: (context, snapshot) {
          if (snapshot.hasError) {
            return const Center(child: Text("Good heavens an error occured!"));
          } else if (snapshot.connectionState == ConnectionState.done) {
            //Multiproviders are not being called?...why?
            return MultiProvider(
              providers: [
                ChangeNotifierProvider<DevOdysseyUserProvider>(
                    create: (_) => DevOdysseyUserProvider()),
                FutureProvider(
                    create: (context) async {
                      await Provider.of<DevOdysseyUserProvider>(context,
                              listen: false)
                          .fetchDoUserFromFirestore(AuthService().user!.uid);
                    },
                    initialData: null)
              ],
              child: MaterialApp(
                routes: appRoutes,
                theme: appTheme,
              ),
            );
          } else {
            return const LoadingScreen();
          }
        },
      ),
    );
  }
}

doUserProvider.dart (Provider + Firebase code)

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dev_odyssey/services/services.dart';
import 'package:flutter/material.dart';

class DevOdysseyUserProvider extends ChangeNotifier {
  DevOdysseyUser? _doUser; // Your DevOdysseyUser instance
  DevOdysseyUser? get doUser => _doUser;

  void setUser(DevOdysseyUser doUser) {
    _doUser = doUser;
    notifyListeners();
  }

  Future<void> fetchDoUserFromFirestore(String userId) async {
    final CollectionReference devOdysseyUsersCollection =
        FirebaseFirestore.instance.collection('devOdysseyUser');

    // Check if the user exists in the Firestore database
    final DocumentSnapshot userSnapshot =
        await devOdysseyUsersCollection.doc(userId).get();

    if (userSnapshot.exists) {
      print("Found user in firestore");
      // User already exists, retrieve the user data
      final doUserData = userSnapshot.data() as Map<String, dynamic>;
      final doUser = DevOdysseyUser.fromJson(doUserData);
      //how do we set this new user to provider
      setUser(doUser); //setting current user to doUser

      final uid = doUser.uid;
      final email = doUser.email;
      print('doUser: $uid');
      print('doUser: $email');
    } else {
      print("Did not find user in firestore");
      // User does not exist, create a new user document in Firestore
      final DevOdysseyUser newDoUser = DevOdysseyUser(
        uid: AuthService().user!.uid,
        email: AuthService().user!.email,
        displayName: AuthService().user?.displayName ?? '',
        photoURL: AuthService().user?.photoURL ?? '',
      );
      await DevOdysseyUserService().addUser(newDoUser);
      setUser(newDoUser);
      final uid = newDoUser.uid;
      final email = newDoUser.email;
      print('newDoUser: $uid');
      print('newDoUser: $email');
    }
  }
}

auth.dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

class AuthService {
  final userStream = FirebaseAuth.instance.authStateChanges();
  final user = FirebaseAuth.instance.currentUser;

  Future<void> anonLogin() async {
    try {
      await FirebaseAuth.instance.signInAnonymously();
    } on FirebaseAuthException catch (e) {
      String errorMessage = 'Failed to sign in anonymously.';
      if (e.code == 'ERROR_NETWORK_REQUEST_FAILED') {
        errorMessage = 'No internet connection. Please check your connection.';
      }
      // Log error message
      // ignore: avoid_print
      print('Error signing in anonymously: $errorMessage');
    }
  }

  Future<void> signOut() async {
    await FirebaseAuth.instance.signOut();
  }

  Future<void> googleLogin() async {
    try {
      final googleUser = await GoogleSignIn().signIn();
      if (googleUser == null) return;
      final googleAuth = await googleUser.authentication;
      final authCrendential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );
      await FirebaseAuth.instance.signInWithCredential(authCrendential);
    } on FirebaseAuthException catch (e) {
      // ignore: avoid_print
      print(e.message);
    }
  }
}

models.dart (only including my custom user class to be concise)

import 'package:json_annotation/json_annotation.dart';
part 'models.g.dart';

@JsonSerializable()
class DevOdysseyUser {
  //from firebase auth user
  final String uid;
  final String? email;
  final String? displayName;
  final String? photoURL;
  //custom properties
  List<DevOdysseyUser> friends;
  String bio;
  int age;
  List<Odyssey> odysseys;

  DevOdysseyUser({
    required this.uid,
    this.email,
    this.displayName = '',
    this.photoURL = '',
    this.friends = const [],
    this.bio = '',
    this.age = 0,
    this.odysseys = const [],
  });

  factory DevOdysseyUser.fromJson(Map<String, dynamic> json) =>
      _$DevOdysseyUserFromJson(json);
  Map<String, dynamic> toJson() => _$DevOdysseyUserToJson(this);
}

Upvotes: 0

Views: 92

Answers (0)

Related Questions