允许提供者之间的区别

Allow distinction between providers

我正在构建一个带有 flutter 和提供者模式的应用程序。我有一个特定的 ViewModel,由 Provider.of<AddressBookModel>(context) 提供。

class HomeScreen extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return ChangeNotifierProvider<AddressBookViewModel>(
          builder:(_) => AddressBookViewModel(),
          child: Scaffold(
              body: _getBody(context);
    }

    Widget _getBody(BuildContext context) {
        AddressBookViewModel vm = Provider.of<AddressBookViewModel>(context);

        // AddressBookViewModel holds a list of contact objects 
        // (id, name, street, starred etc.)
        List<Contact> contacts = vm.contacts; 
        return ListView.builder(
              itemCount: contacts.length,
              itemBuilder: (context, index) => ListTile(
                    title: Text(contacts[index].name),
                    trailing: contacts[index].starred
                        ? Icon(Icons.star))
                        : null,
                        /**
                         * Changing one object rebuilds and redraws the whole list
                         */
                        onLongPress: () => vm.toggleStarred(contacts[index]);
          ));
    }
}

以及各自的ViewModel

class AddressBookViewModel with ChangeNotifier {
    final List<Contact> contacts;

    AddressBookViewModel({this.contacts = []});


    void toggleStarred(Contact contact) {
        int index = contacts.indexOf(contact);
        // the contact object is immutable
        contacts[index] = contact.copy(starred: !contact.starred);
        notifyListeners();
    }
}

我面临的问题是,一旦我用 toggleStarred() 更改列表中的一个联系人对象, 提供商正在重建并重新绘制整个列表。这在我看来是没有必要的,因为只是 一个条目需要重建。有没有办法让提供者只负责 对于一个列表项?或者有什么其他方法可以解决这个问题?

注意:完整代码在文末

第 1 步:使用 ChangeNotifier class

扩展 Contact class
class Contact with ChangeNotifier {  }

第 2 步:删除最终表单 已加星标 字段

  bool starred;

第 3 步:将 toggleStarred 方法从 AddressBookViewModel class 移动到 Contact class

  void toggleStarred() {
    starred = !starred;
    notifyListeners();
  }

步骤 [1,2,3] 代码更改审查:
class Contact with ChangeNotifier {
  final String name;
  bool starred;
  Contact(this.name, this.starred);

  void toggleStarred() {
    starred = !starred;
    notifyListeners();
  }
}

第 4 步:将 ListTile 移动到名为 ContactView 的 StatelessWidget

class ContactView extends StatelessWidget {
   Widget build(BuildContext context) {
    return ListTile();
  }
}

第 5 步:更改 ListView itemBuilder 方法

(context, index) {
return ChangeNotifierProvider.value(
  value: contacts[index],
  child: ContactView(),
);

第 6 步:在新的 StatelessWidget ContactView 上使用 Provider 获取联系人

final contact = Provider.of<Contact>(context);

第 7 步:更改 onLongPress 以使用新的 toggleStarred

onLongPress: () => contact.toggleStarred(),

步骤 [4,6,7] 代码更改审查:
class ContactView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final contact = Provider.of<Contact>(context);
    print("building ListTile item with contact " + contact.name);
    return ListTile(
      title: Text(contact.name),
      trailing: contact.starred ? Icon(Icons.star) : null,
      onLongPress: () => contact.toggleStarred(),
    );
  }
}
步骤 [5] 代码更改审查:
return ListView.builder(
  itemCount: contacts.length,
  itemBuilder: (context, index) {
    print("building ListView item with index $index");
    return ChangeNotifierProvider.value(
      value: contacts[index],
      child: ContactView(),
    );
  },
);

完整代码

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

void main() {
  runApp(
    ChangeNotifierProvider<AddressBookViewModel>(
      builder: (context) => AddressBookViewModel(),
      child: HomeScreen(),
    ),
  );
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<AddressBookViewModel>(
      builder: (context) => AddressBookViewModel(),
      child: MaterialApp(
        home: Scaffold(
          body: _getBody(context),
        ),
      ),
    );
  }

  Widget _getBody(BuildContext context) {
    AddressBookViewModel vm = Provider.of<AddressBookViewModel>(context);

    final contacts = vm.contacts;
    return ListView.builder(
      itemCount: contacts.length,
      itemBuilder: (context, index) {
        print("building ListView item with index $index");
        return ChangeNotifierProvider.value(
          value: contacts[index],
          child: ContactView(),
        );
      },
    );
  }
}

// product_item.dart
class ContactView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final contact = Provider.of<Contact>(context);
    print("building ListTile item with contact " + contact.name);
    return ListTile(
      title: Text(contact.name),
      trailing: contact.starred ? Icon(Icons.star) : null,
      onLongPress: () => contact.toggleStarred(),
    );
  }
}

class AddressBookViewModel with ChangeNotifier {
  final contacts = [
    Contact("Contact A", false),
    Contact("Contact B", false),
    Contact("Contact C", false),
    Contact("Contact D", false),
  ];
  void addcontacts(Contact contact) {
    contacts.add(contact);
    notifyListeners();
  }
}

class Contact with ChangeNotifier {
  final String name;
  bool starred;
  Contact(this.name, this.starred);

  void toggleStarred() {
    starred = !starred;
    notifyListeners();
  }
}

参考:

使用列表时,您需要为列表中的每个项目创建一个 "provider" 并将列表项目提取到一个常量中 – 特别是如果与您的项目关联的数据是不可变的。

而不是:

final contactController = Provider.of<ContactController>(context);
return ListView.builder(
  itemCount: contactController.contacts.length,
  builder: (_, index) {
    reurn Text(contactController.contacts[index].name);
  }
)

更喜欢:

final contactController = Provider.of<ContactController>(context);
return ListView.builder(
  itemCount: contactController.contacts.length,
  builder: (_, index) {
    reurn Provider.value(
      value: contactController.contacts[index],
      child: const ContactItem(),
    );
  }
)

其中 ContactItem 是一个 StatelessWidget,通常看起来像这样:

class ContactItem extends StatelessWidget {
  const ContactItem({Key key}): super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(Provider.of<Contact>(context).name);
  }
}