Flutter-找不到正确的提供者

时间:2020-11-06 21:40:52

标签: flutter

我有一个具有以下文件结构的应用程序:main-> auth-> home-> secret。关键代码如下:

对于main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        StreamProvider<User>.value(value: AuthService().user),
        ChangeNotifierProvider(create: (context) => SecretProvider()),
      ],
      child: MaterialApp(
        title: 'My Secrets',
        home: AuthScreen(),
      ),
    );
  }
}

对于home.dart

class HomeScreen extends StatelessWidget {
  final AuthService _auth = AuthService();

  @override
  Widget build(BuildContext context) {
    var secretProvider = Provider.of<SecretProvider>(context);

    return ChangeNotifierProvider(
      create: (context) => SecretProvider(),
      child: Scaffold(
        appBar: AppBar(
          // some codes...
        ),
        body: StreamBuilder<List<Secret>>(
          stream: secretProvider.secrets,
          builder: (context, snapshot) {
            return Padding(
              padding: EdgeInsets.symmetric(vertical: 15.0),
              child: ListView.separated(
                // return 0 if snapshot.data is null
                itemCount: snapshot.data?.length ?? 0,
                itemBuilder: (context, index) {
                  return ListTile(
                    leading: Icon(Icons.web),
                    title: Text(snapshot.data[index].title),
                    trailing: Icon(Icons.edit),
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => SecretScreen(
                            secret: snapshot.data[index],
                          ),
                        ),
                      );
                    },
                  );
                },
                separatorBuilder: (context, index) {
                  return Divider();
                },
              ),
            );
          },
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecretScreen()),
            );
          },
        ),
      ),
    );
  }
}

对于secret.dart

class SecretScreen extends StatefulWidget {
  final Secret secret;

  SecretScreen({this.secret});

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

class _SecretScreenState extends State<SecretScreen> {
  // some codes...

  @override
  void initState() {
    final secretProvider = Provider.of<SecretProvider>(context, listen: false);

    // some codes...

    super.initState();
  }

  @override
  void dispose() {
    // some codes...

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final secretProvider = Provider.of<SecretProvider>(context);

    return Scaffold(
      // some codes...
    );
  }
}

这些代码工作得很好,但是由于某些类实例生命周期问题,后来我决定将ChangeNotifierProvidermain.dart移到home.dart。新代码如下:

对于main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        StreamProvider<User>.value(value: AuthService().user),
      ],
      child: MaterialApp(
        title: 'My Secrets',
        home: AuthScreen(),
      ),
    );
  }
}

对于home.dart

class HomeScreen extends StatelessWidget {
  final AuthService _auth = AuthService();

  @override
  Widget build(BuildContext context) {
    // var secretProvider = Provider.of<SecretProvider>(context);

    return ChangeNotifierProvider(
      create: (context) => SecretProvider(),
      child: Consumer<SecretProvider>(
        builder: (context, secretProvider, child) {
          return Scaffold(
            appBar: AppBar(
              // some codes...
            ),
            body: StreamBuilder<List<Secret>>(
              stream: secretProvider.secrets,
              // stream: SecretProvider().secrets,
              builder: (context, snapshot) {
                return Padding(
                  padding: EdgeInsets.symmetric(vertical: 15.0),
                  child: ListView.separated(
                    // return 0 if snapshot.data is null
                    itemCount: snapshot.data?.length ?? 0,
                    itemBuilder: (context, index) {
                      return ListTile(
                        leading: Icon(Icons.web),
                        title: Text(snapshot.data[index].title),
                        trailing: Icon(Icons.edit),
                        onTap: () {
                          Navigator.push(
                            context,
                            MaterialPageRoute(
                              builder: (context) => SecretScreen(
                                secret: snapshot.data[index],
                              ),
                            ),
                          );
                        },
                      );
                    },
                    separatorBuilder: (context, index) {
                      return Divider();
                    },
                  ),
                );
              },
            ),
            floatingActionButton: FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => SecretScreen()),
                );
              },
            ),
          );
        },
      ),
    );
  }
}

基本上,我只是将ChangeNotifierProvider移到home.dart并使用Consumer来传递上下文,但是这一次,每当我导航到secret屏幕时,它都会提示我的错误如下:

Could not find the correct Provider<SecretProvider> above this SecretScreen Widget

This likely happens because you used a `BuildContext` that does not include the provider
of your choice.

这个BuildContext确实困扰着我。即使我的ChangeNotifierProvider比以前低了一个级别,SecretScreen小部件仍应该知道从SecretProvider传来的HomeScreen,因为它仍然是HomeScreen,据我所知,上下文应包含SecretProvider

1 个答案:

答案 0 :(得分:2)

出现此错误是因为您的SecretProvider实例是HomeScreen的一部分,而不是SecretScreen的父项。

按顺序,当您推送一个新页面时,该新页面不是上一个页面的后代,因此您无法使用.of(context)方法访问继承的对象。

这里有一个表示小部件树的模式来解释这种情况:

  1. 在MaterialApp(导航器)的顶部具有提供程序:

    Provider
      MaterialApp
        HomeScreen -> push SecretScreen
        SecretScreen -> Here we can acces the Provider by calling Provider.of(context) because the context can access to its ancestors 
    
  2. 使用在HomeScreen中创建的提供程序:

    MaterialApp
      HomeScreen -> push SecretScreen
        Provider -> The provider is part of HomeScreen
      SecretScreen -> The context can't access to the Provider because it's not part of its ancestors
    

我希望我的答案很清楚,并且可以帮助您了解会发生什么;)