Alexandre Fiocre
Alexandre Fiocre

Reputation: 41

Use provider and FutureBuilder not possible?

I try to do this, but it doesn't seem good. If I remove the FutureBuilder and the CircularProgressIndicator it's OK. But it will be good to have this for the time to loading data. Is it possible ?

My source code:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:serialtrip/config/theme.dart';
import 'package:serialtrip/pages/home.dart';
import 'package:serialtrip/pages/login.dart';
import 'package:serialtrip/providers/authProvider.dart';

main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => AuthProvider(),
        ),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Serialtrip',
      debugShowCheckedModeBanner: false,
      theme: defaultTheme,
      home: context.watch<AuthProvider>().loggedInStatus == Status.LoggedIn
          ? Home()
          : FutureBuilder(
              future: context.read<AuthProvider>().autoLogin(),
              builder: (ctx, authResultSnapshot) =>
                  authResultSnapshot.connectionState == ConnectionState.waiting ? CircularProgressIndicator() : Login(),
            ),
    );
  }
}

And the error:

The following assertion was thrown building MyApp(dirty, dependencies: [_InheritedProviderScope<AuthProvider>]):
Tried to use `context.read<AuthProvider>` inside either a `build` method or the `update` callback of a provider.

This is unsafe to do so. Instead, consider using `context.watch<AuthProvider>`.
If you used `context.read` voluntarily as a performance optimisation, the solution
is instead to use `context.select`.
'package:provider/src/provider.dart':
Failed assertion: line 584 pos 9: 'debugIsInInheritedProviderCreate ||
            (!debugDoingBuild && !debugIsInInheritedProviderUpdate)'

Upvotes: 1

Views: 4452

Answers (4)

Alexandre Fiocre
Alexandre Fiocre

Reputation: 41

ok i have found a solution. I use the constructor of AuthProvider to call autoLogin() at the first launch of the app.

main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:serialtrip/config/theme.dart';
import 'package:serialtrip/pages/home.dart';
import 'package:serialtrip/pages/login.dart';
import 'package:serialtrip/pages/splash.dart';
import 'package:serialtrip/providers/authProvider.dart';

main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => AuthProvider(),
        ),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Serialtrip',
      debugShowCheckedModeBanner: false,
      theme: defaultTheme,
      home: _showScreen(context),
    );
  }
}

Widget _showScreen(BuildContext context) {
  switch (context.watch<AuthProvider>().loggedInStatus) {
    case Status.Authenticating:
      return Splash();
    case Status.LoggedIn:
      return Home();
    default:
      return Login();
  }
}

authProvider.dart

import 'package:flutter/widgets.dart';

enum Status { NotLoggedIn, Authenticating, LoggedIn }

class AuthProvider with ChangeNotifier {
  Status _loggedInStatus = Status.NotLoggedIn;

  Status get loggedInStatus => _loggedInStatus;

  /// Constructor
  AuthProvider() {
    _autoLogin();
  }

  /// Auto-login
  Future<void> _autoLogin() async {
    _loggedInStatus = Status.Authenticating;
    notifyListeners();
    print('autologin - waiting');

    await new Future.delayed(const Duration(seconds: 5));

    _loggedInStatus = Status.LoggedIn;
    notifyListeners();
    print('autologin - sucess');
  }

  /// Login
  Future<void> login(String email, String password) async {
    _loggedInStatus = Status.Authenticating;
    notifyListeners();
    print('login - waiting');

    await new Future.delayed(const Duration(seconds: 2));

    _loggedInStatus = Status.LoggedIn;
    notifyListeners();
    print('login - sucess');
  }

  /// Logout
  Future<void> logout() async {
    _loggedInStatus = Status.NotLoggedIn;
    notifyListeners();
    print('logout - sucess');
  }
}

Upvotes: 2

ASAD HAMEED
ASAD HAMEED

Reputation: 2900

First Possibility

In order to se an affect of the autoLogin function, you need to use Consumer

Widget _showScreen(BuildContext context) {
    return Consumer<Status>(
             builder: (context, loginStatus, child) {
                   switch (loginStatus) {
                      case Status.Authenticating:
                            return Splash();
                      case Status.LoggedIn:
                            return Home();
                      default:
                            return Login();
                               }
                      }

                       );
}
    

Update your autoLogin function as follows.

Future<Status> autoLogin() async {

    await new Future.delayed(const Duration(seconds: 2));

    _loggedInStatus = Status.LoggedIn;
 
    return _loggedInStatus;
  }

Second Possibility(Recommended)

If you want to listen to loggedInState changes continuously, you need StreamProvider since FutureProvier listens to changes only once.

In this case,

MyApp Class

class MyApp extends StatelessWidget {
     
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Serialtrip',
            debugShowCheckedModeBanner: false,
            theme: defaultTheme,
            home: StreamProvider(
              create: (_) => AuthProvider().autoLogin(),
              initialData: Status.Authenticating,
              child: _showScreen(context),
            ));
      }
    }

AutoLogin Stream You need to change AutoLogin from future to stream

Stream<Status> autoLogin() async* {
    _loggedInStatus = Status.Authenticating;
    yield _loggedInStatus;

    await new Future.delayed(const Duration(seconds: 2));

    _loggedInStatus = Status.LoggedIn;
    yield _loggedInStatus;
  }

Consumer remains unchanged.

Widget _showScreen(BuildContext context) {
    return Consumer<Status>(
             builder: (context, loginStatus, child) {
                   switch (loginStatus) {
                      case Status.Authenticating:
                            return Splash();
                      case Status.LoggedIn:
                            return Home();
                      default:
                            return Login();
                               }
                      }

                       );
}

Upvotes: 0

Alexandre Fiocre
Alexandre Fiocre

Reputation: 41

My last try with FutureProvider but autologin is not call :(

main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:serialtrip/config/theme.dart';
import 'package:serialtrip/pages/home.dart';
import 'package:serialtrip/pages/login.dart';
import 'package:serialtrip/pages/splash.dart';
import 'package:serialtrip/providers/authProvider.dart';

main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => AuthProvider(),
        ),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Serialtrip',
        debugShowCheckedModeBanner: false,
        theme: defaultTheme,
        home: FutureProvider(
          create: (_) => AuthProvider().autoLogin(),
          initialData: Status.Authenticating,
          child: _showScreen(context),
        ));
  }
}

Widget _showScreen(BuildContext context) {
  switch (context.watch<AuthProvider>().loggedInStatus) {
    case Status.Authenticating:
      return Splash();
    case Status.LoggedIn:
      return Home();
    default:
      return Login();
  }
}

authProvider.dart

import 'package:flutter/widgets.dart';

enum Status { NotLoggedIn, LoggedIn, Authenticating, LoggedOut }

class AuthProvider with ChangeNotifier {
  Status _loggedInStatus;
  String _token;

  Status get loggedInStatus => _loggedInStatus;
  String get token => _token;

  Future<String> autoLogin() async {
    _loggedInStatus = Status.Authenticating;
    notifyListeners();
    print('autologin - waiting');

    await new Future.delayed(const Duration(seconds: 2));

    _token = 'abcd1234';
    _loggedInStatus = Status.LoggedIn;
    notifyListeners();
    print('autologin - sucess');

    return _token;
  }

  Future login(String email, String password) async {
    _loggedInStatus = Status.Authenticating;
    notifyListeners();
    print('login - waiting');

    await new Future.delayed(const Duration(seconds: 2));

    _token = 'abcd1234';
    _loggedInStatus = Status.LoggedIn;
    notifyListeners();
    print('login - sucess');
  }

  Future logout() async {
    _token = null;
    _loggedInStatus = Status.LoggedOut;
    notifyListeners();
    print('logout - sucess');
  }
}

Upvotes: 1

EdwynZN
EdwynZN

Reputation: 5601

Tried to use context.read<AuthProvider> inside either a build method or the update callback of a provider

If you read the documentation you will notice that it discourage you tu use context.read inside the build method, use Provider.of<AuthProvider>(context, listen: false).autoLogin();

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Serialtrip',
      debugShowCheckedModeBanner: false,
      theme: defaultTheme,
      home: context.watch<AuthProvider>().loggedInStatus == Status.LoggedIn
          ? Home()
          : FutureBuilder(
              future: Provider.of<AuthProvider>(context, listen: false).autoLogin(),
              builder: (ctx, authResultSnapshot) =>
                  authResultSnapshot.connectionState == ConnectionState.waiting ? CircularProgressIndicator() : Login(),
            ),
    );
  }
}

Upvotes: 0

Related Questions