Flutter - How to pass user data to all views
Asked Answered
F

5

45

I'm new to the flutter world and mobile app development and struggling with how I should pass user data throughout my app.

I've tried several things, but none seem great and I'm sure there are best practice patterns I should be following.

Because it makes examples easier, I'm using firebase for authentication. I currently have a separate route for logging in. Once I'm logged in I want the User model in most views for checking permissions on what to show, displaying user info in the drawer, etc...

Firebase has an await firebaseAuth.currentUser(); Is it best practice to call this everywhere you might need the user? and if so, where is the best spot to place this call?

The flutter codelab shows a great example of authenticating users before allowing writes. However, if the page needs to check auth to determine what to build, the async call can't go in the build method.

initState

One method I've tried is to override initState and kick off the call to get the user. When the future completes I call setState and update the user.

    FirebaseUser user;

    @override
    void initState() {
      super.initState();
      _getUserDetail();
    }

  Future<Null> _getUserDetail() async {
    User currentUser = await firebaseAuth.currentUser();
    setState(() => user = currentUser);
  }

This works decent but seems like a lot of ceremony for each widget that needs it. There is also a flash when the screen loads without the user and then gets updated with the user upon the future's completion.

Pass the user through the constructor

This works too but is a lot of boilerplate to pass the user through all routes, views, and states that might need to access them. Also, we can't just do popAndPushNamed when transitioning routes because we can't pass a variable to it. We have to change routes similar to this:

Navigator.push(context, new MaterialPageRoute(
    builder: (BuildContext context) => new MyPage(user),
));

Inherited Widgets

https://medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1

This article showed a nice pattern for using InheritedWidget. When I place the inherited widget at the MaterialApp level, the children aren't updating when the auth state changed (I'm sure I'm doing it wrong)

  FirebaseUser user;

  Future<Null> didChangeDependency() async {
    super.didChangeDependencies();
    User currentUser = await firebaseAuth.currentUser();
    setState(() => user = currentUser);
  }

  @override
  Widget build(BuildContext context) {
    return new UserContext(
      user,
      child: new MaterialApp(
        title: 'TC Stream',
        theme: new ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: new LoginView(title: 'TC Stream Login', analytics: analytics),
        routes: routes,
      ),
    );
  }

FutureBuilder

FutureBuilder also seems like a decent option but seems to be a lot of work for each route. In the partial example below, _authenticateUser() is getting the user and setting state upon completion.

  @override
  Widget build(BuildContext context) {
    return new FutureBuilder<FirebaseUser>(
      future: _authenticateUser(),
      builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return _buildProgressIndicator();
        }
        if (snapshot.connectionState == ConnectionState.done) {
          return _buildPage();
        }
      },
    );
  }

I'd appreciate any advice on best practice patterns or links to resources to use for examples.

Foredo answered 28/10, 2017 at 13:22 Comment(1)
There are lot's different way. But InheritedWidget is interesting feature of Flutter for data propagation. You can find an example of InheritedWidget here gitlab.com/brianegan/scoped_model . Also you can consider to use Redux pub.dartlang.org/search?q=reduxMartyry
H
21

I'd recommend investigating inherited widgets further; the code below shows how to use them with asynchronously updating data:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(new MaterialApp(
      title: 'Inherited Widgets Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
          appBar: new AppBar(
            title: new Text('Inherited Widget Example'),
          ),
          body: new NamePage())));
}

// Inherited widget for managing a name
class NameInheritedWidget extends InheritedWidget {
  const NameInheritedWidget({
    Key key,
    this.name,
    Widget child}) : super(key: key, child: child);

  final String name;

  @override
  bool updateShouldNotify(NameInheritedWidget old) {
    print('In updateShouldNotify');
    return name != old.name;
  }

  static NameInheritedWidget of(BuildContext context) {
    // You could also just directly return the name here
    // as there's only one field
    return context.inheritFromWidgetOfExactType(NameInheritedWidget);
  }
}

// Stateful widget for managing name data
class NamePage extends StatefulWidget {
  @override
  _NamePageState createState() => new _NamePageState();
}

// State for managing fetching name data over HTTP
class _NamePageState extends State<NamePage> {
  String name = 'Placeholder';

  // Fetch a name asynchonously over HTTP
  _get() async {
    var res = await http.get('https://jsonplaceholder.typicode.com/users');
    var name = json.decode(res.body)[0]['name'];
    setState(() => this.name = name); 
  }

  @override
  void initState() {
    super.initState();
    _get();
  }

  @override
  Widget build(BuildContext context) {
    return new NameInheritedWidget(
      name: name,
      child: const IntermediateWidget()
    );
  }
}

// Intermediate widget to show how inherited widgets
// can propagate changes down the widget tree
class IntermediateWidget extends StatelessWidget {
  // Using a const constructor makes the widget cacheable
  const IntermediateWidget();

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new Padding(
        padding: new EdgeInsets.all(10.0),
        child: const NameWidget()));
  }
}

class NameWidget extends StatelessWidget {
  const NameWidget();

  @override
  Widget build(BuildContext context) {
    final inheritedWidget = NameInheritedWidget.of(context);
    return new Text(
      inheritedWidget.name,
      style: Theme.of(context).textTheme.display1,
    );
  }
}
Hindustani answered 14/11, 2017 at 23:35 Comment(3)
Oh interesting, thanks for the example! My latest attempts have been to wrap the Scaffold in an InheritedWidget so the information is available in the drawer, appBar, and the body. I've been playing with the FutureBuilder in conjunction with the inherited widgets for loading the content.Foredo
Have you tried using onAuthStateChanges with a StreamBuilder?Praxiteles
I stuck with a problem with InheritedWidget – it can't be used from initState(). Moving it to didChangeDependencies() helps, but then it gets called multiple times, even if the widget is no longer active. In my case, InheritedWidget holds API wrapper, so that solution doesn't work.Seduction
B
7

I prefer to use Services with Locator, using Flutter get_it.

Create a UserService with a cached data if you like:

class UserService {
  final Firestore _db = Firestore.instance;
  final String _collectionName = 'users';
  CollectionReference _ref;

  User _cachedUser; //<----- Cached Here

  UserService() {
    this._ref = _db.collection(_collectionName);
  }

  User getCachedUser() {
    return _cachedUser;
  }

  Future<User> getUser(String id) async {
    DocumentSnapshot doc = await _ref.document(id).get();

    if (!doc.exists) {
      log("UserService.getUser(): Empty companyID ($id)");
      return null;
    }

    _cachedUser = User.fromDocument(doc.data, doc.documentID);
    return _cachedUser;
  }
}

Then create create a Locator

GetIt locator = GetIt.instance;

void setupLocator() {
  locator.registerLazySingleton(() => new UserService());
}

And instantiate in main()

void main() {
  setupLocator();
  new Routes();
}

That's it! You can call your Service + cachedData everywhere using:

.....
UserService _userService = locator<UserService>();

@override
void initState() {
  super.initState();
  _user = _userService.getCachedUser();
}
Blackmon answered 14/10, 2019 at 23:0 Comment(1)
how to user this approch when the user reopen the app the _cachedUser will be destroy?Burcham
R
1

I crashed into another problem because of this problem you can check it out here So the solution I came up with is a bit untidy,I created a separate Instance dart page and imported it to every page.

 GoogleSignInAccount Guser = googleSignIn.currentUser;
 FirebaseUser Fuser;

I stored the user there on login and checked on every StateWidget if it was null

  Future<Null> _ensureLoggedIn() async {

if (Guser == null) Guser = await googleSignIn.signInSilently();
if (Fuser == null) {
  await googleSignIn.signIn();
  analytics.logLogin();
}
if (await auth.currentUser() == null) {
  GoogleSignInAuthentication credentials =
  await googleSignIn.currentUser.authentication;
  await auth.signInWithGoogle(
    idToken: credentials.idToken,
    accessToken: credentials.accessToken,
  );
}

This is my old code I did cleaned it up on my current app but I don't have that code now in handy. Just check out for null user and log it in again

I did it for most of the Firebase instances too because I have more than 3 pages on my app and Inherited Widgets was just too much work

Roasting answered 7/3, 2018 at 5:4 Comment(0)
R
1

You can use the GetX package to check whether or not the user is logged in, get user data and have it accessible throughout your app

Reticent answered 24/9, 2020 at 20:28 Comment(1)
how can we do that?Yong
S
-2

For my lazy mathod, i just create new file like userdata.dart and then put any variable on it for example like dynamic Profile = null

inside userdata.dart

//only put this or anything u want.
dynamic Profile = null;

at startingpage.dart

//import that file
import '../userdata.dart';

class startingpage extends ...{
...
//set data to store..
   Profile = 'user profile';
...
}

to use the data just declare and use in anotherpage.dart

//import that file
import '../userdata.dart';

class anotherpage extends...{
...
}

class .. State ...{
...
//set the data to variable
   dynamic userdata = Profile;
   print('this is my lazy pass data' + userdata.toString());
...
}
Scamander answered 10/4, 2019 at 4:26 Comment(1)
you can give share preference example in it.General

© 2022 - 2024 — McMap. All rights reserved.