Flutter Riverpod and ListView

eye-catch Dart and Flutter

I didn’t know how to use a Riverpod provider for ListView, so I looked into it and implemented it. If you are looking for a solution this post helps.

The Riverpod version is 1.0.0. If you want to check the code for 0.14.0+3, open the toggle.

Sponsored links

Dynamic item creation with Riverpod

Look at the following video first to know the final behavior. Ignore the title of the view. It’s copy and paste mistake.

The view is very simple. The number of items increases when pressing the plus button. To come to the point, it works if we use a provider for itemCount property. Let’s check the code.

The top Widget is the following.

class _View1 extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    return Scaffold(
      appBar: AppBar(title: Text("List with StateProvider")),
      body: Column(children: [
        Center(child: button),
        Expanded(child: listView),
      ]),
    );
  }
}
class _View1 extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: Text("List with StateProvider")),
      body: Column(children: [
        Center(child: button),
        Expanded(child: listView),
      ]),
    );
  }
}

There are two widgets in the body. We want to create a new item when the button is pressed.

Update a provider value to trigger the rebuild

So we need to have the item list and add the new string there in the onPressed event.


final button = IconButton(
  onPressed: () {
    _texts.add("string-${_texts.length + 1}");
  },
  icon: Icon(Icons.add),
);

We need to trigger the rebuild by updating a provider value. Riverpod offers several providers but I choose StateProvider here. I wanted to write the following code at first but other variables can’t be used in the initializer.

final List<String> _texts = [];
final StateProvider<int> _lengthProvider = StateProvider((ref) => _texts.length);
// The instance member '_texts' can't be accessed in an initializer.
// Try replacing the reference to the instance member with a different
// expression dart (implicit_this_reference_in_initializer)

Therefore, we need to set 0 in the initializer. Let’s update the value in the onPressed event to trigger the rebuild for the consumer.

final List<String> _texts = [];
final StateProvider<int> _lengthProvider = StateProvider((ref) => 0); // added
@override
Widget build(BuildContext context, ScopedReader watch) {
  final button = IconButton(
    onPressed: () {
      _texts.add("string-${_texts.length + 1}");
      context.read(_lengthProvider).state = _texts.length;  // added
    },
    icon: Icon(Icons.add),
  );
  ...
}
final List<String> _texts = [];
final StateProvider<int> _lengthProvider = StateProvider((ref) => 0); // added

@override
Widget build(BuildContext context, WidgetRef ref) {
  final button = IconButton(
    onPressed: () {
      _texts.add("string-${_texts.length + 1}");
      ref.read(_lengthProvider.state).state = _texts.length; // added
    },
    icon: Icon(Icons.add),
  );
  ...
}

A consumer reads the provider value to rebuild it

We’ve implemented a button to update the number of items. The next step is to implement a consumer.

@override
Widget build(BuildContext context, ScopedReader watch) {
  ...
  final listView = ListView.builder(
    itemCount: watch(_lengthProvider).state,
    itemBuilder: (context, index) {
      return ListTile(
        title: Text(_texts[index] + "/${_texts.length}"),
      );
    },
  );
}
@override
Widget build(BuildContext context, WidgetRef ref) {
  ...
  final listView = ListView.builder(
    itemCount: ref.watch(_lengthProvider.state).state,
    itemBuilder: (context, index) {
      return ListTile(
        title: Text(_texts[index] + "/${_texts.length}"),
      );
    },
  );
}

If we want to rebuild the target widget, we need to use watch function. I first tried to use it in itemBuilder function but it doesn’t work. Instead, assign the provider value to itemCount. When the value changes ListView is rebuilt.

The following code is the final code.

class _View1 extends ConsumerWidget {
  final List<String> _texts = [];
  final StateProvider<int> _lengthProvider = StateProvider((ref) => 0);

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final button = IconButton(
      onPressed: () {
        _texts.add("string-${_texts.length + 1}");
        context.read(_lengthProvider).state = _texts.length;
      },
      icon: Icon(Icons.add),
    );

    final listView = ListView.builder(
      itemCount: watch(_lengthProvider).state,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(_texts[index] + "/${_texts.length}"),
        );
      },
    );

    return Scaffold(
      appBar: AppBar(title: Text("List wth StateProvider")),
      body: Column(children: [
        Center(child: button),
        Expanded(child: listView),
      ]),
    );
  }
}
class _View1 extends ConsumerWidget {
  final List<String> _texts = [];
  final StateProvider<int> _lengthProvider = StateProvider((ref) => 0);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final button = IconButton(
      onPressed: () {
        _texts.add("string-${_texts.length + 1}");
        ref.read(_lengthProvider.state).state = _texts.length;
      },
      icon: Icon(Icons.add),
    );

    final listView = ListView.builder(
      itemCount: ref.watch(_lengthProvider.state).state,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(_texts[index] + "/${_texts.length}"),
        );
      },
    );

    return Scaffold(
      appBar: AppBar(title: Text("List with StateProvider")),
      body: Column(children: [
        Center(child: button),
        Expanded(child: listView),
      ]),
    );
  }
}
Sponsored links

Dynamic Provider creation for ListView

We learnt how to use a provider with ListView. How can we implement if we want to update only one widget in the list view? Let’s try to do it.

This is the final behavior.

Each item has a counter to know how many times it’s pressed. To update the target item, a provider list is needed. Let’s define the following.

final List<StateProvider<int>> _countProviderList = [];

The new provider needs to be created dynamically and added to the provider list when pressing a plus button.

final button = IconButton(
  onPressed: () {
    _texts.add("string-${_texts.length + 1}");
    _countProviderList.add(StateProvider((ref) => 0));  // added
    context.read(_lengthProvider).state = _texts.length;
  },
  icon: Icon(Icons.add),
);
final button = IconButton(
  onPressed: () {
    _texts.add("string-${_texts.length + 1}");
    _countProviderList.add(StateProvider((ref) => 0));
    ref.read(_lengthProvider.state).state = _texts.length;
  },
  icon: Icon(Icons.add),
);

The next step is to know which item is pressed. The order of the list corresponds to the order of the items. Therefore, we can use the index in the itemBuilder function.

final listView = ListView.builder(
  itemCount: watch(_lengthProvider).state,
  itemBuilder: (context, index) {
    final text = _texts[index] +
        "/${_texts.length}" +
        "\t${watch(_countProviderList[index]).state.toString()}";   // added
    return ListTile(
      title: Text(text),
      onTap: () => context.read(_countProviderList[index]).state++, // added
    );
  },
);
final listView = ListView.builder(
  itemCount: ref.watch(_lengthProvider.state).state,
  itemBuilder: (context, index) {
    final text = _texts[index] +
        "/${_texts.length}" +
        "\t${ref.watch(_countProviderList[index].state).state.toString()}";  // added
    return ListTile(
      title: Text(text),
      onTap: () => ref.read(_countProviderList[index].state).state++,  //added
    );
  },
);

I thought at first that it didn’t work because watch function is used outside of the widget declaration but it works.

End

You can find the complete code here.

https://github.com/yuto-yuto/flutter_samples/blob/main/lib/riverpod/riverpod_with_list.dart

Comments

  1. gew says:

    Thanks for your good articles.
    The codes you mentioned “complete code” in github is currently available?

    looks like not work.

    • Yuto Yuto says:

      Thank you for your comment. I ran the code again but it worked.
      Did you clone my repository?

      If you copied and pasted it, it might not work because the latest flutter_riverpod version is 1.0.0.
      I haven’t checked the changelog yet. I will try to update the version this weekend.

Copied title and URL