Разрешить различие между поставщиками

Я создаю приложение с флаттером и шаблоном провайдера. У меня есть конкретная модель 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(), провайдер перестраивает и перерисовывает весь список. На мой взгляд, в этом нет необходимости, поскольку необходимо перестроить только одну запись. Есть ли способ иметь поставщика, который отвечал бы только за один элемент списка? Или каким-то другим способом решить эту проблему?


person thehayro    schedule 18.07.2019    source источник


Ответы (2)


При работе со списками вам нужно иметь «поставщика» для каждого элемента вашего списка и извлекать элемент списка в константу, особенно если данные, связанные с вашим элементом, неизменяемы.

Вместо того:

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);
  }
}
person Rémi Rousselet    schedule 23.07.2019
comment
Спасибо за ваш ответ. Однако ваш ответ подразумевает, что объект Contact также является ChangeNotifier, поскольку я хочу иметь возможность пометить объект звездочкой. Но я вижу несогласованность в дизайне этого: когда ContactController содержит список контактов и является ChangeNotifier, я предполагаю, что могу решить это с помощью другого контроллера, который отвечает за один единственный объект Contact и выполняет операцию toggle(). Впрочем, у меня нет проблем с вашим решением, если нет другого способа решить эту проблему. - person thehayro; 24.07.2019
comment
Это вовсе не означает, что Contact ChangeNotifier, нет. Я использовал Provider, а не ChangeNotifierProvider :) - person Rémi Rousselet; 24.07.2019

Примечание: полный код доступен в конце

gistshowng показывает приложение, работающее с журналами

Шаг 1. Расширьте класс Contact с помощью класса ChangeNotifier

class Contact with ChangeNotifier {  }

Шаг 2. Удалите помеченное поле финальной формы

  bool starred;

Шаг 3: переместите метод toggleStarred из класса AddressBookViewModel в класс Contact

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

Steps[1,2,3] Code Changes Review :
class Contact with ChangeNotifier {
  final String name;
  bool starred;
  Contact(this.name, this.starred);

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

Шаг 4: переместите ListTile в виджет StatelessWidget под названием ContactView

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 получите контакт с помощью поставщика

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

Шаг 7: измените onLongPress, чтобы использовать новый toggleStarred

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

Steps[4,6,7] Code Changes Review :
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(),
    );
  }
}
Steps[5] Code Changes Review :
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();
  }
}

Ссылка:

person Mohamed Elrashid    schedule 21.07.2019