dfdfdsfsd
dfdfdsfsd

Reputation: 327

multi Provider doesn't work in material app in flutter?

I've created an app and I have used MultiProvider but it doesn't work when I use it inside MaterialApp

I want to use it to change app theme color but

it gives me an error:

*Note: when I use posts provider in any other screen it works.

My Code:

import 'package:blog_app/provider/posts.dart';
import 'package:blog_app/provider/settings.dart';
import 'package:blog_app/screens/splash_screen.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<Posts>(
          builder: (context) => Posts(),
        ),
        ChangeNotifierProvider<Settings>(
          builder: (context) => Settings(),
        ),
      ],
      child: MaterialApp(
        darkTheme: Provider.of<Settings>(context).darkModeEnabled ? ThemeData.dark() : ThemeData.light(),
        debugShowCheckedModeBanner: false,
        title: 'Blogy',
        theme: ThemeData(
          primaryColor: Colors.deepPurple[900],
          cursorColor: Colors.deepPurple[900],
          accentColor: Colors.deepPurple[900],
          fontFamily: 'Ubuntu',
        ),
        home: SplashScreen(),
      ),
    );
  }
}

The Error :-

I/flutter ( 9316): The following ProviderNotFoundError was thrown building MyApp(dirty):
I/flutter ( 9316): Error: Could not find the correct Provider<Settings> above this MyApp Widget
I/flutter ( 9316):
I/flutter ( 9316): To fix, please:
I/flutter ( 9316):
I/flutter ( 9316):   * Ensure the Provider<Settings> is an ancestor to this MyApp Widget
I/flutter ( 9316):   * Provide types to Provider<Settings>
I/flutter ( 9316):   * Provide types to Consumer<Settings>
I/flutter ( 9316):   * Provide types to Provider.of<Settings>()

Upvotes: 6

Views: 10238

Answers (2)

Quacke
Quacke

Reputation: 253

This error happens because you are creating your providers and your consumers in the same build method. This results in them having the same context, which doesn't have any Provider<Settings> registered yet. Provider.of<Settings>(context) is trying to find a Provider<Settings> above in the widget tree, but there is no such provider there.

Using Consumer seems like a valid workaround, but recreating the whole MaterialApp on every change might be pretty heavy.

I suggest instead creating separate widgets for providers and the app root:

class AppProviders extends StatelessWidget {
  final Widget child;

  AppProviders({this.child});

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<Posts>(
          builder: (context) => Posts(),
        ),
        ChangeNotifierProvider<Settings>(
          builder: (context) => Settings(),
        ),
      ],
      child: child;
    );
  }
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      darkTheme: Provider.of<Settings>(context).darkModeEnabled ? ThemeData.dark() : 
      ThemeData.light(),
      debugShowCheckedModeBanner: false,
      title: 'Blogy',
      theme: ThemeData(
        primaryColor: Colors.deepPurple[900],
        cursorColor: Colors.deepPurple[900],
        accentColor: Colors.deepPurple[900],
        fontFamily: 'Ubuntu',
      ),
      home: SplashScreen(),
    );
  }
}

Then wrap the MyApp widget in AppProviders inside the runApp function.

void main() {
  runApp(
    AppProviders(
      child: MyApp(),
    )
  );
}

This ensures that the Providers are registered above your root app widget and that they are visible in its context.

Alternatively you can declare three widgets where the third one is just AppProviders(child: MyApp()) and call that one in runApp. Note that creating AppProviders inside MyApp's build method will result in the same error as before, so don't try it that way.

Upvotes: 4

chunhunghan
chunhunghan

Reputation: 54397

The following test code work without error, you can test with your case
Use Consumer to wrap MaterialApp

code snippet

return MultiProvider(
      providers: [
        ChangeNotifierProvider<Posts>(
          create: (context) => Posts(),
        ),
        ChangeNotifierProvider<Settings>(
          create: (context) => Settings(darkModeEnabled: true),
        ),
      ],
      child: Consumer<Settings>(builder: (_, settings, child) {
        return MaterialApp(
          darkTheme:
              settings.darkModeEnabled ? ThemeData.dark() : ThemeData.light(),
          debugShowCheckedModeBanner: false,
          title: 'Blogy',
          theme: ThemeData(
            primaryColor: Colors.deepPurple[900],
            cursorColor: Colors.deepPurple[900],
            accentColor: Colors.deepPurple[900],
            fontFamily: 'Ubuntu',
          ),
          home: SplashScreen(),
        );
      }),
    );

full test code

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

void main() => runApp(MyApp());

class Posts extends ChangeNotifier {}

class Settings extends ChangeNotifier {
  bool darkModeEnabled;
  Settings({this.darkModeEnabled});
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<Posts>(
          create: (context) => Posts(),
        ),
        ChangeNotifierProvider<Settings>(
          create: (context) => Settings(darkModeEnabled: true),
        ),
      ],
      child: Consumer<Settings>(builder: (_, settings, child) {
        return MaterialApp(
          darkTheme:
              settings.darkModeEnabled ? ThemeData.dark() : ThemeData.light(),
          debugShowCheckedModeBanner: false,
          title: 'Blogy',
          theme: ThemeData(
            primaryColor: Colors.deepPurple[900],
            cursorColor: Colors.deepPurple[900],
            accentColor: Colors.deepPurple[900],
            fontFamily: 'Ubuntu',
          ),
          home: SplashScreen(),
        );
      }),
    );
  }
}

class SplashScreen extends StatefulWidget {
  SplashScreen({Key key}) : super(key: key);

  //final String title;

  @override
  _SplashScreenState createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("test"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Upvotes: 8

Related Questions