Reputation: 225
I'm struggling to figure out how to use Riverpod for the following scenario.
I have a ListView where the children are Containers with a button inside.
When the button is pressed, I want to change the colour of that container. I want that colour to be stored in a piece of state using a Riverpod provider.
There is another button outside the list. When that is pressed, it should change the color of ALL the containers.
It feels like I need a Change/StateNotifierProvider for each container. Do I use families for this? How would I tie a particular piece of state to its associated container?
And how would the red button access all of the states to change the colour of all?
As a bonus, I would also like the red button to be notified when one of the green buttons changes the color of its container
Many thanks
Upvotes: 8
Views: 9117
Reputation: 31
Alex's non-hooks version is a bit different with riverpod ^1.0.0
I changed one small thing independent of version: I moved the provider out of the class to global scope, both approaches work, the official docs show this version below.
class ContainerListState extends StateNotifier<List<Model>> {
ContainerListState() : super(const []);
// No static provider declaration in here
...
}
// Provider moved out here
final containerListProvider = StateNotifierProvider<ContainerListState, List<Model>>((ref) {
return ContainerListState();
});
ProviderScope is necessary for the app to still be able to access the provider.
void main() {
runApp(const ProviderScope(child: MyApp()));
}
Changes from 0.12 to 1.0 regarding this question:
context.read()
for StatelessWidget
-> Use ConsumerWidget
WidgetRef ref
and ref.watch()
instead of ScopedReader watch
Non-hooks:
With riverpod ^1.0.4
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final modelList = ref.watch(containerListProvider);
return Scaffold(
appBar: AppBar(
title: Text('ListView of Containers'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () {
ref.read(containerListProvider.notifier).addItem();
},
),
],
),
body: ListView.builder(
itemCount: modelList.length,
itemBuilder: (_, index) {
return ContainerWithButton(model: modelList[index]);
},
),
floatingActionButton: RedButton(),
);
}
}
class ContainerWithButton extends ConsumerWidget {
const ContainerWithButton({
Key? key,
required this.model,
}) : super(key: key);
final Model model;
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
tileColor: model.color,
trailing: ElevatedButton(
style: ElevatedButton.styleFrom(primary: Colors.lightGreen),
onPressed: () {
ref
.read(containerListProvider.notifier)
.setModelColor(model, Colors.purple);
},
child: Text('Button'),
),
);
}
}
class RedButton extends ConsumerWidget {
const RedButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
// Bonus: Red button will be notified on changes
final state = ref.watch(containerListProvider);
return FloatingActionButton.extended(
onPressed: () {
ref.read(containerListProvider.notifier).setAllColor(Colors.orange);
},
backgroundColor: Colors.red,
label: Text('Set all color'),
);
}
}
Upvotes: 3
Reputation: 6000
You could use family, but in this case, since you have a non-fixed number of entries it would complicate things unnecessarily.
Here is a full runnable example written with hooks_riverpod. If you need me to translate to not use hooks I can do that too. Keep in mind this is intentionally simple and a bit naive, but should be adaptable to your situation.
First off, a model class. I would typically use freezed but that's out of scope for this question.
class Model {
final int id;
final Color color;
Model(this.id, this.color);
}
Next, the StateNotifier:
class ContainerListState extends StateNotifier<List<Model>> {
ContainerListState() : super(const []);
static final provider = StateNotifierProvider<ContainerListState, List<Model>>((ref) {
return ContainerListState();
});
void setAllColor(Color color) {
state = state.map((model) => Model(model.id, color)).toList();
}
void setModelColor(Model model, Color color) {
final id = model.id;
state = state.map((model) {
return model.id == id ? Model(id, color) : model;
}).toList();
}
void addItem() {
// TODO: Replace state.length with your unique ID
state = [...state, Model(state.length, Colors.lightBlue)];
}
}
Lastly, the UI components (hooks):
class MyHomePage extends HookWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final modelList = useProvider(ContainerListState.provider);
return Scaffold(
appBar: AppBar(
title: Text('ListView of Containers'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () {
context.read(ContainerListState.provider.notifier).addItem();
},
),
],
),
body: ListView.builder(
itemCount: modelList.length,
itemBuilder: (_, index) {
return ContainerWithButton(model: modelList[index]);
},
),
floatingActionButton: RedButton(),
);
}
}
class ContainerWithButton extends StatelessWidget {
const ContainerWithButton({
Key? key,
required this.model,
}) : super(key: key);
final Model model;
@override
Widget build(BuildContext context) {
return ListTile(
tileColor: model.color,
trailing: ElevatedButton(
style: ElevatedButton.styleFrom(primary: Colors.lightGreen),
onPressed: () {
context.read(ContainerListState.provider.notifier).setModelColor(model, Colors.purple);
},
child: Text('Button'),
),
);
}
}
class RedButton extends HookWidget {
const RedButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Bonus: Red button will be notified on changes
final state = useProvider(ContainerListState.provider);
return FloatingActionButton.extended(
onPressed: () {
context.read(ContainerListState.provider.notifier).setAllColor(Colors.orange);
},
backgroundColor: Colors.red,
label: Text('Set all color'),
);
}
}
Non-hooks:
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, ScopedReader watch) {
final modelList = watch(ContainerListState.provider);
return Scaffold(
appBar: AppBar(
title: Text('ListView of Containers'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () {
context.read(ContainerListState.provider.notifier).addItem();
},
),
],
),
body: ListView.builder(
itemCount: modelList.length,
itemBuilder: (_, index) {
return ContainerWithButton(model: modelList[index]);
},
),
floatingActionButton: RedButton(),
);
}
}
class ContainerWithButton extends StatelessWidget {
const ContainerWithButton({
Key? key,
required this.model,
}) : super(key: key);
final Model model;
@override
Widget build(BuildContext context) {
return ListTile(
tileColor: model.color,
trailing: ElevatedButton(
style: ElevatedButton.styleFrom(primary: Colors.lightGreen),
onPressed: () {
context.read(ContainerListState.provider.notifier).setModelColor(model, Colors.purple);
},
child: Text('Button'),
),
);
}
}
class RedButton extends ConsumerWidget {
const RedButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, ScopedReader watch) {
// Bonus: Red button will be notified on changes
final state = watch(ContainerListState.provider);
return FloatingActionButton.extended(
onPressed: () {
context.read(ContainerListState.provider.notifier).setAllColor(Colors.orange);
},
backgroundColor: Colors.red,
label: Text('Set all color'),
);
}
}
I would recommend throwing this in a new Flutter app to test it out.
Upvotes: 9