Flutter Provider 提供同一对象的多个实例

问题描述 投票:0回答:1

下面的代码(可以直接在 dartpad 中复制/粘贴)根据类别列表创建 3 个选项卡,在本例中:

'People', 'Cars', 'Animals'

该应用程序无法正常工作,因为如果您单击“计数”按钮,显然所有页面上的计数器都会增加。每个选项卡页的计数器应分别增加。所以我想每页有一个单独的

Data
实例,但我不知道如何在 Flutter 中做到这一点。

我无法为每个类别创建继承的数据对象,因为对于我的真实应用程序,我事先不知道类别。

有什么想法吗?

此代码可以直接复制/粘贴到https://dartpad.dev/

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Data>(
        create: (context) => Data(),
        child: MaterialApp(
          title: _title,
          home: MyHomePage(),
        ));
  }
}

class MyHomePage extends StatelessWidget {
  List<String> categories = ['People', 'Cars', 'Animals'];
  List<Tab> getTabs(int tabsCount) {
    List<Tab> tabs = [];
    for (var category in categories) {
      tabs.add(
        Tab(text: category),
      );
    }
    return tabs;
  }

  @override
  Widget build(BuildContext context) {
    var tabs = getTabs(4);
    var data = Provider.of<Data>(context);
    return DefaultTabController(
      length: tabs.length,
      child: Builder(builder: (BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabs: tabs,
            ),
          ),
          body: TabBarView(
            children: tabs.map((Tab tab) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    tab.text!,
                    style: Theme.of(context).textTheme.headline5,
                  ),
                  const SizedBox(height: 30),
                  Text(
                    data.counter.toString(),
                    style: Theme.of(context).textTheme.headline3,
                  ),
                  const SizedBox(height: 30),
                  ElevatedButton(
                      onPressed: data.increment, child: const Text('Count'))
                ],
              );
            }).toList(),
          ),
        );
      }),
    );
  }
}

class Data extends ChangeNotifier {
  int counter = 0;

  void increment() {
    counter++;
    notifyListeners();
  }
}
flutter-provider
1个回答
0
投票

所以我想出了以下解决方案,请参阅下面的代码,您可以直接在 DartPad 中运行:我已将

ChangeNotifierProvider
移动到直接环绕标签页。这样您就可以拥有同一类型的多个实例。 tabPage 在树中查找提供者并找到最近的一个。

不过,我认为提供者模式不是很灵活。在下面的示例中,每次选项卡页移出视图时都会处理数据提供程序,并在移入视图时重新创建数据提供程序。明天我将在我的真实应用程序中测试这一点。

您可以直接在 dartpad 中复制/粘贴以下代码并自行测试:

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  List<MyExtendedTab> getTabs() {
    List<MyExtendedTab> tabs = [];
    for (var category in Repo.categories) {
      tabs.add(
        MyExtendedTab(category),
      );
    }
    return tabs;
  }

  @override
  Widget build(BuildContext context) {
    var tabs = getTabs();
    return DefaultTabController(
      length: tabs.length,
      child: Builder(builder: (BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabs: tabs,
            ),
          ),
          body: TabBarView(
            children: //tabPages
                tabs.map((MyExtendedTab tab) {
              // Moved the ChangeNotifierProvider here to directly wrap the tabpage(s) so the tab page will find its corresponding ChangeNotifierProvider when moving op in the widget tree
              // this way we can have multiple provider instances of the same type
              return ChangeNotifierProvider<Data>(
                  // Here the Data object (ChangeNotifier) is instantiated with an id. This id determines which data to retrieve.
                  create: (context) => Data(
                        category: tab.category,
                      ),
                  child: Consumer<Data>(builder: (context, data, child) {
                    return Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          '${data.category.name} ${data.category.id}',
                          style: Theme.of(context).textTheme.headline5,
                        ),
                        const SizedBox(height: 30),
                        Text(
                          data.counter.toString(),
                          style: Theme.of(context).textTheme.headline3,
                        ),
                        const SizedBox(height: 30),
                        ElevatedButton(
                            onPressed: data.increment,
                            child: const Text('Count'))
                      ],
                    );
                  }));
            }).toList(),
          ),
        );
      }),
    );
  }
}

class Data extends ChangeNotifier {
  final Category category;
  Data({required this.category}) {
    print('Data.constructor id:${category.id}');
  }

  int get counter {
    print(
        'Data.get counter id:${category.id}; Repo.counters:${Repo.counters.length}');
    return Repo.counters[category.id];
  }

  void increment() {
    Repo.counters[category.id]++;
    notifyListeners();
    print('counters: ${Repo.counters}');
  }
}

// the mockup repo: this is where the data is 'stored'.
class Repo {
  static List<Category> categories = [
    Category(id: 0, name: 'People'),
    Category(id: 1, name: 'Cars'),
    Category(id: 2, name: 'Animals')
  ];

  // just a list of integers to store the counter values. 1st int is for People, 2nd int for Cars, etc.
  static List<int> counters = [0, 0, 0];
}

class Category {
  final int id;
  final String name;

  Category({required this.id, required this.name});
}

// I need the Id in the tab to work with, hence an extendedTab
class MyExtendedTab extends Tab {
  final Category category;
  MyExtendedTab(this.category, {super.key}) : super(text: category.name);
}

TL;DR 我也看过 RiverPod,但它似乎只适用于简单类型?尽管文档很丰富,但提供的代码示例充其量也很模糊。我最终编写了这样的代码:

onPressed: ref.read(counterProvider(category).notifier).increment,

而不是

onPressed: data.increment,

我更喜欢后者...

© www.soinside.com 2019 - 2024. All rights reserved.