How to get StreamProvider data in children with new routes in Flutter (Dart)

Issue

I am using the StreamProvider method to wrap my widgets with certain data, such as Auth (which is working anywhere in my app) from Firebase Auth. I want to do the same with a Firestore value but it only seems to work one level deep.

I have a database call that finds an employees profile once the auth check is done. When I try get the employee from my Home() widget with Provider.of(context) it works great:

This is my wrapper widget (which is my main file’s home: widget)

class Wrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    final user = Provider.of<User>(context);
    print(user.uid);

    // Return either home or authenticate widget
    if (user == null) {
      return Authenticate();
    }
    else {
      return StreamProvider<Employee>.value(
        value: DatabaseService().linkedEmployee(user.uid),
        child: Home(),
      );
    }
  }
}

The Database Service function from DatabaseService():

// Get Linked Employee
  Stream<Employee> linkedEmployee(String uid) {
    return employeesCollection.where("linkedUser", isEqualTo: uid).snapshots().map(_linkedEmployeeFromSnapShot);
  }
  
  Employee _linkedEmployeeFromSnapShot(QuerySnapshot snapshot) {
    final doc = snapshot.documents[0];
    return Employee(
        eId: doc.data["eId"],
        employeeCode: doc.data["employeeCode"],
        fName: doc.data["fName"],
        lName: doc.data["lName"],
        docId: doc.documentID
    );
  }

I can access Provider.of<User>(context) from any widget anywhere in my tree. So why can’t I do the same for Provider.of<Employee>(context) ?

When I try that in any widget other than Home() I get the error:

Error: Could not find the correct Provider above this Vehicles Widget

For example, in my widget Vehicles:

class Vehicles extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    final user = Provider.of<User>(context);
    final employee = Provider.of<Employee>(context);
    ...

The User Provider works fine, I can print it out, but the employee provider does not work.

Is it something to do with context? Thanks, any advice would be appreciated.

How I’m navigating to the Vehicles() widget from Home() with a raised button with this event :

onPressed: () {
  Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => Vehicles())
   );
  },

Solution

Here is a more explained reply hence I think some encounter this issue and I also think it’s a bit tricky to get the head around it, especially when you have rules in your Firestore that requires a user to be authorized to access the database.

But generally, you want to wrap providers (that you want to access around all of the app) around MaterialApp().

So I’ll show you a simple example to easier understand it.

//The App() handles makes the providers globally accessible
class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FirebaseAuthProviderLayer(
      child: AuthorizedProviderLayer(
        authorizedChild: MatApp(child: StartSwitch()),
        unAuthorizedChild: MatApp(child: SignInScreen()),
      ),
    );
  }
}

//The MaterialApp Wrapped so that it not has to be rewritten
class MatApp extends StatelessWidget {
  Widget child;

  MatApp({this.child});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'App',
      home: child,
    );
  }
}

class FirebaseAuthProviderLayer extends StatelessWidget {
  final Widget child;

  FirebaseAuthProviderLayer({this.child});

  @override
  Widget build(BuildContext context) {
    return StreamProvider<User>.value(
      value: FirebaseAuth.instance.authStateChanges(),
      child: child,
    );
  }
}

//And the layer that decides either or not we should attach all the providers that requires the user to be authorized.
class AuthorizedProviderLayer extends StatelessWidget {
  Widget authorizedChild;
  Widget unAuthorizedChild;

  AuthorizedProviderLayer({this.unAuthorizedChild, this.authorizedChild});

  User user;
  final FirestoreService firestoreService =
      FirestoreService(); //The Service made to access Firestore

  @override
  Widget build(BuildContext context) {
    user = Provider.of<User>(context);
    if (user is User)
      return MultiProvider(
        providers: [
          StreamProvider<FirestoreUserData>.value(
            value: firestoreService.streamUser(),
          ),
          StreamProvider<AppSettings>.value(
            value: firestoreService.streamSettings(),
            initialData: null,
          )
        ],
        child: authorizedChild,
      );
    return unAuthorizedChild;
  }
}

Answered By – Max Allan Niklasson

Answer Checked By – Mildred Charles (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.