Is there a way to rebuild AnimatedList in Flutter? - flutter

I have the following issue with my 'workout' App using multiple workoutlists with various workoutitems:
I select a workoutlist with 12 workoutitems.
The 'activity' screen with the AnimatedList is shown.
Afterwards, I select a different workoutlist with 80 workoutitems.
The AnimatedList is now showing the new workoutlist but only the first 12 workoutitems.
Why?
I thought that the AnimatedList inside the build Widget is rebuild every time (I am not using GlobalKey).
class WorkoutListView extends StatelessWidget {
const WorkoutListView({this.filename});
final String filename;
#override
Widget build(BuildContext context) {
return Selector<WorkoutListModel, List<Workout>>(
selector: (_, model) => model.filterWorkouts(filename),
builder: (context, workouts, _) {
return AnimatedWorkoutList(
list: workouts,
);
},
);
}
}
class AnimatedWorkoutList extends StatefulWidget {
const AnimatedWorkoutList({
Key key,
#required List<Workout> list,
}) : _list = list,
super(key: key);
final List<Workout> _list;
#override
_AnimatedWorkoutListState createState() => _AnimatedWorkoutListState();
}
class _AnimatedWorkoutListState extends State<AnimatedWorkoutList> {
#override
Widget build(BuildContext context) {
return AnimatedList(
initialItemCount: widget._list.length,
itemBuilder: (context, index, animation) {
final workout = widget._list[index];
return Column(
children: [
// Using AnimatedList.of(context).removeItem() for list manipulation
],
);
},
);
}
}

try this:
class AnimatedWorkoutList extends StatefulWidget {
const AnimatedWorkoutList({
#required List<Workout> list,
});
final List<Workout> list;
#override
_AnimatedWorkoutListState createState() => _AnimatedWorkoutListState();
}
class _AnimatedWorkoutListState extends State<AnimatedWorkoutList> {
#override
Widget build(BuildContext context) {
return AnimatedList(
initialItemCount: widget.list.length,
itemBuilder: (context, index, animation) {
final workout = widget.list[index];
return Column(
children: [
// Using AnimatedList.of(context).removeItem() for list manipulation
],
);
},
);
}
}

Related

Pass Index from Carousel Widget to Another Widget Flutter

I am new to Flutter and stumped on how to do this. I have a screen that has a Carousel Slider widget on it that I am holding in a separate file/widget to keep the code as clean as possible. To that Carousel I am passing a List which are urls of images and videos. I have already implemented an indicator bar and have the index of the list held within activeIndex variable within the Carousel widget. I then need to pass that index value to a separate widget held in a variable on the main page of my app (one with the clean code).
I basically need help on where to define variables in one widget that I can then define and pass to multiple other widgets. Please let me know if you need more context as I am new to coding in general. Thanks!
Carousel Widget
class AssetCarouselBuilder extends StatefulWidget {
const AssetCarouselBuilder({
#required this.assets,
this.activeIndex
});
final List<String> assets;
final int activeIndex;
#override
State<AssetCarouselBuilder> createState() => _AssetCarouselBuilderState();
}
class _AssetCarouselBuilderState extends State<AssetCarouselBuilder> {
int activeIndex = 0;
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CarouselSlider.builder(itemCount: widget.assets.length,
itemBuilder: (context, index, realIndex){
final assetUrl = widget.assets[index];
options: CarouselOptions(
onPageChanged: (index, reason) =>
setState (() => activeIndex = index)
//this is where I am holding the variable 'activeIndex' that I need elsewhere
if (assetUrl.contains('jpg')) {
return buildImage(assetUrl, index);
}
const SizedBox(height: 5),
buildIndicator(),
Widget buildImage(String imageUrl, int index) => Image(),
Widget buildIndicator() => AnimatedSmoothIndicator(
activeIndex: activeIndex,
count: widget.assets.length,
effect: ColorTransitionEffect()
Implementation of Carousel on "main page"
class FeedPageWidget extends StatefulWidget {
const FeedPageWidget({
Key key,
}) : super(key: key);
#override
_FeedPageWidgetState createState() => _FeedPageWidgetState();
}
class _FeedPageWidgetState extends State<FeedPageWidget>
int _currentIndex = 0;
AssetCarouselBuilder(assets: listViewPostsRecord.postAssets.asList())
And then widget I need to pass the index to another widget on the "main page".
ShareMenuWidget(
postRef: listViewPostsRecord,
assetIndex: _currentIndex)
Any help on how I get the "activeIndex" value on the setState function in the Carousel slider is very appreciated!
You can use callback method like Function(int activeIndex)? onIndexChanged;.
class CarouselCW extends StatefulWidget {
final Function(int activeIndex)? onIndexChanged;
const CarouselCW({
Key? key,
this.onIndexChanged,
}) : super(key: key);
#override
State<CarouselCW> createState() => _CarouselCWState();
}
class _CarouselCWState extends State<CarouselCW> {
final CarouselController carouselController = CarouselController();
#override
Widget build(BuildContext context) {
return CarouselSlider.builder(
itemCount: 4,
itemBuilder: (BuildContext context, int index, int realIndex) {
return Text(
index.toString(),
);
},
options: CarouselOptions(
onPageChanged: (index, reason) {
if (widget.onIndexChanged != null) widget.onIndexChanged!(index);
},
),
);
}
}
And while using this widget you will get
CarouselCW(
onIndexChanged: (activeIndex) {
print(activeIndex.toString());
},
)

how to display the value that came from valueNotifier?

I have a valueNotifier that generates a list of events and takes a random string every 5 seconds and sends it to the screen. It lies in inheritedWidget. How can I display in the ListView the event that came with the valueNotifier? What is the correct way to print the answer?
My code:
class EventList extends StatelessWidget {
const EventList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return EventInherited(
child: EventListScreen(),
);
}
}
class EventListScreen extends StatefulWidget {
const EventListScreen({Key? key}) : super(key: key);
#override
State<EventListScreen> createState() => _EventListScreenState();
}
class _EventListScreenState extends State<EventListScreen> {
#override
Widget build(BuildContext context) {
final eventNotifier = EventInherited.of(context).eventNotifier;
return Scaffold(
appBar: AppBar(
title: const Text('Event List'),
centerTitle: true,
),
body: Container(
padding: const EdgeInsets.all(30),
child: ValueListenableBuilder(
valueListenable: eventNotifier,
builder: (BuildContext context, List<String> value, Widget? child) {
return ListView(
children: [
],
);
},
),
),
);
}
}
class EventNotifier extends ValueNotifier<List<String>> {
EventNotifier(List<String> value) : super(value);
final List<String> events = ['add', 'delete', 'edit'];
final stream = Stream.periodic(const Duration(seconds: 5));
late final streamSub = stream.listen((event) {
value.add(
events[Random().nextInt(4)],
);
});
}
class EventInherited extends InheritedWidget {
final EventNotifier eventNotifier = EventNotifier([]);
EventInherited({required Widget child}) : super(child: child);
static EventInherited of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType()!;
}
#override
bool updateShouldNotify(EventInherited oldWidget) {
return oldWidget.eventNotifier.streamSub != eventNotifier.streamSub;
}
}
If you have correct value, you can return listview like this:
return ListView.builder(
itemCount: value.length,
itemBuilder: (context, index) {
return Text(value[index]);
},
);
After having a quick look at ValueNotifier,
It says the following:
When the value is replaced with something that is not equal to the old value as evaluated by the equality operator ==, this class notifies its listeners.
In your case, the value is an array. By adding items to the array, it wont recognise a change.
Also see other Stacko post.
Try something like:
value = [...value].add(...)

ChangeNotifierProvider not re-rendering the UI (Flutter,Dart,Provider)

I have a BaseView that contains ChangeNotifieProvider and Consumer which will be common to use anywhere. This Widget also receives Generic types of ViewModel. It has onModelReady that to be called inside init state.
Also using get_it for Dependency injection.
Issue: Whenever the user inserts a new entry and calls fetch data, data gets loaded but UI still remains as it is.
If I remove the ChangeNotifierProvider and use only Consumer then it's re-rendering UI in a proper way. But I cannot pass the onModelReady function that is to be called in initState()
:::::::::::CODE:::::::::::::::::::::::::
base_view.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:businesshub/injections/injection_container.dart';
import 'package:businesshub/features/views/viewmodels/base_model.dart';
class BaseView<T extends BaseModel> extends StatefulWidget {
const BaseView({
Key? key,
this.onModelReady,
required this.builder,
}) : super(key: key);
final Function(T)? onModelReady;
final Widget Function(BuildContext context, T model, Widget? child) builder;
#override
_BaseViewState<T> createState() => _BaseViewState();
}
class _BaseViewState<T extends BaseModel> extends State<BaseView<T>> {
T model = locator<T>();
#override
void initState() {
super.initState();
if (widget.onModelReady != null) {
widget.onModelReady!(model);
}
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>(
create: (context) => model,
child: Consumer<T>(
builder: widget.builder,
),
);
}
}
USING::::::::::::::::HERE::::::::::::::::::::::
class RecentBillBuilder extends StatelessWidget {
const RecentBillBuilder({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return BaseView<SalesBillViewModel>(
onModelReady: (model) {
model.fetchAndSetSalesBills(currentUser!.uid);
model.searchController.clear();
},
builder: (ctx, model, _) {
if (model.state == ViewState.busy) {
return Center(
child: CircularProgressIndicator.adaptive(),
);
}
return model.bills.fold(
(l) => ResourceNotFound(title: l.message!),
(r) => (r.isEmpty)
? ResourceNotFound(title: "Sales Bills not created yet!")
: ListView.builder(
itemCount: min(r.length, 7),
shrinkWrap: true,
reverse: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (ctx, index) {
return RecentBillsCard(bill: r[index]);
},
),
);
},
);
}
}

Updating and animating an AnimatedList with Provider in Flutter

I'm able to successfully animate an AnimatedList's contents in Flutter when the list data is stored in the same component that owns the list widget (i.e., there's no rebuild happening when there's changes to the list data). I run into issues when I try to get the items for the list from a ChangeNotifier using Provider and Consumer.
The component that owns the AnimatedList, let's call it ListPage, is built with a Consumer<ListItemService>. My understanding is that ListPage is then rebuilt whenever the service updates the list data and calls notifyListeners(). When that happens, I'm not sure where within ListPage I could call AnimatedListState.insertItem to animate the list, since during the build the list state is still null. The result is a list that doesn't animate its contents.
I think my question boils down to "how do I manage state for this list if the contents are fetched and updated in real time?", and ideally I'd like to understand what's going on but I'm open to suggestions on how I should change this if this isn't the best way to approach the task.
Here's some code that illustrates the problem:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<AuthService>(
create: (_) => AuthService(),
),
ChangeNotifierProxyProvider<AuthService, ListItemService>(
create: (_) => ListItemService(),
update: (_, authService, listItemService) =>
listItemService!..update(authService),
),
],
child: MaterialApp(
home: HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<ListItemService>(
builder: (context, listItemService, _) =>
ListPage(items: listItemService.items),
);
}
}
// Implementation details aren't really relevant, but
// this only updates if the user logs in or out.
class AuthService extends ChangeNotifier {}
class ListItemService extends ChangeNotifier {
List<Item> _items = [];
List<Item> get items => _items;
Future<void> update(AuthService authService) async {
// Method that subscribes to a Firestore snapshot
// and calls notifyListeners() after updating _items.
}
}
class Item {
Item({required this.needsUpdate, required this.content});
final String content;
bool needsUpdate;
}
class ListPage extends StatefulWidget {
const ListPage({Key? key, required this.items}) : super(key: key);
final List<Item> items;
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
late int _initialItemCount;
#override
void initState() {
_initialItemCount = widget.items.length;
super.initState();
}
void _updateList() {
for (int i = 0; i < widget.items.length; i++) {
final item = widget.items[i];
if (item.needsUpdate) {
// _listKey.currentState is null here if called
// from the build method.
_listKey.currentState?.insertItem(i);
item.needsUpdate = false;
}
}
}
#override
Widget build(BuildContext context) {
_updateList();
return AnimatedList(
key: _listKey,
initialItemCount: _initialItemCount,
itemBuilder: (context, index, animation) => SizeTransition(
sizeFactor: animation,
child: Text(widget.items[index].content),
),
);
}
}
You can use didUpdateWidget and check the difference between the old and new list. "Checking the difference" means looking at what has been removed vs added. In you case the Item widget should have something to be identified. You can use Equatable for example so that an equality between Items is an equality between their properties.
One other important aspect is that you are dealing with a list, which is mutable, but Widgets should be immutable. Therefore it is crucial that whenever you modify the list, you actually create a new one.
Here are the implementations details, the most interesting part being the comment of course (though the rendering is fun as well ;)):
import 'dart:async';
import 'dart:math';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<AuthService>(
create: (_) => AuthService(),
),
ChangeNotifierProxyProvider<AuthService, ListItemService>(
create: (_) => ListItemService(),
update: (_, authService, listItemService) => listItemService!..update(authService),
),
],
child: MaterialApp(
home: HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Material(
child: SafeArea(
child: Consumer<ListItemService>(
builder: (context, listItemService, _) => ListPage(
// List.from is very important because it creates a new list instead of
// giving the old one mutated
items: List.from(listItemService.items),
),
),
),
);
}
}
// Implementation details aren't really relevant, but
// this only updates if the user logs in or out.
class AuthService extends ChangeNotifier {}
class ListItemService extends ChangeNotifier {
List<Item> _items = [];
List<Item> get items => _items;
Future<void> update(AuthService authService) async {
// Every 5 seconds
Timer.periodic(Duration(seconds: 5), (timer) {
// Either create or delete an item randomly
if (Random().nextDouble() > 0.5 && _items.isNotEmpty) {
_items.removeAt(Random().nextInt(_items.length));
} else {
_items.add(
Item(
needsUpdate: true,
content: 'This is item with random number ${Random().nextInt(10000)}',
),
);
}
notifyListeners();
});
}
}
class Item extends Equatable {
Item({required this.needsUpdate, required this.content});
final String content;
bool needsUpdate;
#override
List<Object?> get props => [content]; // Not sure you want to include needsUpdate?
}
class ListPage extends StatefulWidget {
const ListPage({Key? key, required this.items}) : super(key: key);
final List<Item> items;
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
final _listKey = GlobalKey<AnimatedListState>();
// You can use widget if you use late
late int _initialItemCount = widget.items.length;
/// Handles any removal of [Item]
_handleRemovedItems({
required List<Item> oldItems,
required List<Item> newItems,
}) {
// If an [Item] was in the old but is not in the new, it has
// been removed
for (var i = 0; i < oldItems.length; i++) {
final _oldItem = oldItems[i];
// Here the equality checks use [content] thanks to Equatable
if (!newItems.contains(_oldItem)) {
_listKey.currentState?.removeItem(
i,
(context, animation) => SizeTransition(
sizeFactor: animation,
child: Text(oldItems[i].content),
),
);
}
}
}
/// Handles any added [Item]
_handleAddedItems({
required List<Item> oldItems,
required List<Item> newItems,
}) {
// If an [Item] is in the new but was not in the old, it has
// been added
for (var i = 0; i < newItems.length; i++) {
// Here the equality checks use [content] thanks to Equatable
if (!oldItems.contains(newItems[i])) {
_listKey.currentState?.insertItem(i);
}
}
}
// Here you can check any update
#override
void didUpdateWidget(covariant ListPage oldWidget) {
super.didUpdateWidget(oldWidget);
_handleAddedItems(oldItems: oldWidget.items, newItems: widget.items);
_handleRemovedItems(oldItems: oldWidget.items, newItems: widget.items);
}
#override
Widget build(BuildContext context) {
return AnimatedList(
key: _listKey,
initialItemCount: _initialItemCount,
itemBuilder: (context, index, animation) => SizeTransition(
sizeFactor: animation,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(widget.items[index].content),
),
),
);
}
}

Best practice to update value from another class

I am new to flutter, so please excuse my experience.
I have 2 classes, both stateful widgets.
One class contains the tiles for a listview.
Each tile class has a checkbox with a state bool for alternating true or false.
The other class (main) contains the body for creating the listview.
What I'd like to do is retrieve the value for the checkbox in the main class, and then update a counter for how many checkbboxes from the listview tiles have been checked, once a checkbox value is updated. I am wondering what the best practices are for doing this.
Tile class
class ListTile extends StatefulWidget {
#override
_ListTileState createState() => _ListTileState();
}
class _ListTileState extends State<ListTile> {
#override
Widget build(BuildContext context) {
bool selected = false;
return Container(
child: Row(
children: [Checkbox(value: selected, onChanged: (v) {
// Do something here
})],
),
);
}
}
Main Class
class OtherClass extends StatefulWidget {
#override
_OtherClassState createState() => _OtherClassState();
}
class _OtherClassState extends State<OtherClass> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text("Checkbox selected count <count here>"),
ListView.builder(itemBuilder: (context, index) {
// Do something to get the selected checkbox count from the listview
return ListTile();
}),
],
),
);
}
}
Hope this is you are waiting for
class OtherClass extends StatefulWidget {
#override
_OtherClassState createState() => _OtherClassState();
}
class _OtherClassState extends State<OtherClass> {
bool selected = false;
#override
void initState() {
super.initState();
}
var items = [
Animal("1", "Buffalo", false),
Animal("2", "Cow", false),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("title")),
body: Container(
child: ListView.builder(
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (ctx, i) {
return Row(
children: [
Text(items[i].name),
ListTile(
id: items[i].id,
index: i,
)
],
);
}),
));
}
}
ListTileClass
class ListTile extends StatefulWidget {
final String? id;
final int? index;
final bool? isSelected;
const ListTile ({Key? key, this.id, this.index, this.isSelected})
: super(key: key);
#override
_ListTileState createState() => _ListTileState();
}
class _ListTileState extends State<ListTile> {
bool? selected = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: 20,
child: Checkbox(
value: selected,
onChanged: (bool? value) {
setState(() {
selected = value;
});
}));
}
}
I'd recommend using a design pattern such as BLoC or using the Provider package. I personally use the Provider Package. There are plenty of tutorials on youtube which can help get you started.