Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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>()

like image 855
dfdfdsfsd Avatar asked Dec 09 '19 18:12

dfdfdsfsd


People also ask

Is flutter provider a design pattern?

Flutter developers use various design patterns to write clean and maintainable codebases. They often write reusable widget implementations in individual Dart files, separate the main app screens into different files, and decompose large and isolated widgets into private methods/classes.


2 Answers

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),
      ),
    );
  }
}
like image 96
chunhunghan Avatar answered Nov 15 '22 05:11

chunhunghan


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.

like image 36
Quacke Avatar answered Nov 15 '22 07:11

Quacke