Animations in List with async data provided by a Stream - flutter

I have a Firestore collection with 4 different documents. In my app I have a list that shows every document in a ListView. I get the documents through a Stream. Users can delete, move and add items. When a user moves an item I want it to animate the way that the item moves above/below other items (the moving system is made through up votes and down votes, the most upvoted items being at the top, while the most downvoted being at the bottom). When an item gets 1 upvote more that the item on top of it, it should animate the switch of the items. I know that animated list does not have any native support for this. How can I get to tell my list to do an animation when I get new data? Right now everything looks confusing and everything is jumping instantly because the normal ListView does not provide any animations for moving/inserting/removing data. Given that I work with async data and streams that process becomes harder. Any ideas on how I can detect changes in the stream and animate the current list to the new state of the list? Whether that means removing/inserting/modifying the positions of some items.

Use implicitly animated list
https://pub.dev/packages/implicitly_animated_reorderable_list
// Specify the generic type of the data in the list.
ImplicitlyAnimatedList<MyGenericType>(
// The current items in the list.
items: items,
// Called by the DiffUtil to decide whether two object represent the same item.
// For example, if your items have unique ids, this method should check their id equality.
areItemsTheSame: (a, b) => a.id == b.id,
// Called, as needed, to build list item widgets.
// List items are only built when they're scrolled into view.
itemBuilder: (context, animation, item, index) {
// Specifiy a transition to be used by the ImplicitlyAnimatedList.
// See the Transitions section on how to import this transition.
return SizeFadeTransition(
sizeFraction: 0.7,
curve: Curves.easeInOut,
animation: animation,
child: Text(item.name),
);
},
// An optional builder when an item was removed from the list.
// If not specified, the List uses the itemBuilder with
// the animation reversed.
removeItemBuilder: (context, animation, oldItem) {
return FadeTransition(
opacity: animation,
child: Text(oldItem.name),
);
},
);

Related

How to animate ListView rendering in flutter?

Okay so there are a couple of questions already asked regarding this but no one is doing what I want to do.
Context :
I've a bloc which maintains a stateCounter. Now whenever user visits the onboarding page, they are shown three different subpages (not exactly pages but 3 different content items). i.e.,
stateCounter == 1 | Fetch image from map at index = stateCounter + Fetch title from map at index = stateCounter + Fetch subtitle from map at index = stateCounter
When user clicks Next button, stateCounter increases and the Map's next child is shown which re-renders my page (to move to the next item).
I'm also using a Dismissble widget on each child so rendered, so that user can also increase the stateCounter when he/she drags from end to start (to achieve a sliding effect)
Now everything works like a charm but the only problem here is that when the next data is rendered (when stateCounter increases and we fetch the next series of image, title and subtitle), the change is not so subtle.
Here, I would want to show some kind of animation so that It looks good to the user. How can i do that ??
Here is the ListView that gets rendered on the screen based on stateCounter value:
ListView onBoardingSubscreens(stateCounter, context, bloc, controller) {
return ListView(
children: [
skipBtn(context, bloc),
renderImage(stateCounter, 0, context),
const SpaceRenderer(
heightFactor: 3,
),
renderScroller(stateCounter, 1, context),
const SpaceRenderer(
heightFactor: 3,
),
titleText(context, stateCounter),
const SpaceRenderer(
heightFactor: 2,
),
captionText(stateCounter),
Timer(
controller: controller,
secondsRemaining: 6,
onFinished: () {
stateChangeLogic(stateCounter, context, bloc, controller);
controller.restart();
},
),
],
);
}
As I mentioned above, this is rendered inside : ConstrainedBox which is rendered inside a Dismissible and a Column in the end.
Kindly help. I already tried using AnimatedContainer etc. but no animations are being shown. I'm open to using a package (if needed)

Ensure the visibility of the first item in a horizontal listview after prepending some items

I have a horizontal list view where I detect if the user has scrolled to the leftmost or rightmost edge and prepend, or append, new list items respectively.
The code structure is like so:
class WidgetState extends State<SomeWidget> {
// This list is prepended with some items when the user scrolls to the left edge
// (i.e when position.pixels = 0)
var _list;
// I use the scroll controller to detect if the user has hit the leftmost or rightmost edge
// of the list and trigger the logic that updates the above list
var _scrollController;
#override
Widget build(BuildContext context) {
ListView.separated(
itemBuilder: (context, index) {
return _list[ index ];
},
controller: _scrollController
);
}
}
The issue is that the new prepended items will push the old items outside of the visible view. What I would like to do is somehow preserve the scroll position such that the user still has to scroll to the left to see the now newly prepended items.
One idea I had is to use https://pub.dev/packages/scrollable_positioned_list and scroll to the index of the first old item in the list. However, I didn't want to add a whole new list implementation just for this. Is there a better way?

Flutter Animated List: Conditionally add ListView item

I am have an animated list in my flutter project.
For every element in that list I have a grid of buttons that are placed dynamically from a Firestore stream. Sometimes that will come back with 10 items in the grid but other times that will comeback with 0 items.
When a button on the grid in a list element is pushed it will search firestore and create a new grid of buttons in the next list element below.
The problem that I have is when it comes back with 0 grid buttons I don't want it to create a new list element (an empty list element with no grid buttons). I tried returning a container with 0 size as a work around but animated list still gives it some height so you can see there is a problem. I also understand that this would be bad practice as you have non visible empty list elements in the list.
I start with a list of foods as strings:
List foodListGrids = ['breads','drinks']
I then have an animated list:
AnimatedList(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
key: _FoodandDrinkKey,
initialItemCount: foodListGrids.length,
itemBuilder: (context, index, animation) {
return SizeTransition(
sizeFactor: animation,
child: buildButtonGridItemsMenu(index),
);
},
),
I set the AnimatedList size to the length of the foods list.
I set the child of the Animated List to a class that searches firebase and returns a card with the grid of buttons on it like this:
StreamBuilder(
stream: Firestore.instance
.collection(widget.categoryType)
.where(widget.relationship, isEqualTo: widget.searchString)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Container(width: 0, height: 0,);
} else if (snapshot.hasData) {
List<Widget> widgetList = [];
List<DocumentSnapshot> documentList = snapshot.data.documents;
if (documentList.length > 0) {
for (int i = 0; i < documentList.length; i++) {
widgetList.add(ButtonTheme(
minWidth: 16,
height: 30,
child: GridButton(snapshot, i, widget.listIndex),
));
}
return Container(
width: double.infinity,
child: Wrap(
children: widgetList,
alignment: WrapAlignment.center,
));
} else{
return Text('NO DATA BECAUSE NUMBER OF GRID ITEMS IS 0');
}
} else {
return Text('NO DATA BECAUSE GRID ITEMS CALL IS NULL');
}
},
),
then in the on pressed method for each grid button I add a new list element like this:
void _insertCategoryGridItem(String id, int index) {
if (!foodListGrids.contains(id)) {
foodListGrids.add(id);
_FoodandDrinkKey.currentState.insertItem(index + 1);
}
}
The problem is a chicken or the egg problem I will try to show below:
List item is generated from the index 0 in the food list and all stream data is if from food list index 0 firebase results.
On pressed for a grid item in the first list row is pressed to add a new list row with a new set of grid items. This will then update the food list array and the call for the list to add new row of grid buttons. The issue is because this is in the onpressed for the first rows grid there is no knowledge of what will be returned for the next row so there is no way of knowing if it will return a grid of size 0 in the next list row in the current way it is setup.
I have tried returning null, container of 0 width and height but have had no luck. I am not sure what I can do to fix it.
Thanks for your help
I'm not sure if I get you right but seems that I faced the same problem with AnimatedList and stream of data from the Firestore. The problem is in initialItemCount: property of the AnimatedList.
In my case I wanted to change AnimtedList in two ways:
I wanted to manually add an item and to show it with animation.
I want that if the list is changed due to a new portion of data from the stream - I want the list to be updated without animation of inserting and without errors (out of range).
To solve this I did a dirty hack: when there is an update from the stream I reinit the key of the list, in your case it's _FoodandDrinkKey. So BEFORE you build the AnmatedList just reinit your key _listKeyUserNotes = GlobalKey(); that's how the List will "forget" about it's initialItemCount and will render a new data without any out-of-range errors.
When you want to add a new item manually with animation - use insert().
key: _FoodandDrinkKey,
initialItemCount: foodListGrids.length,
Hope this makes sense.

How to dynamically add Children to Scaffold Widget

Let's say, I have a chat screen that looks like this.
Now, when the user clicks the "Press when ready" button, the method fetchNewQuestion() is called.
My intention is that this will make a HTTP request, and display the result using
_buildUsersReply(httpResponse);
But, the problem is that this return must be made inside the current scaffold's widget as a child under the existing children, so that it is built at the bottom with the previous ones still there. The result would be like this:
You can find my complete code here.
Is this possible to be done pro-grammatically? Or do I have to change the concept of how I do this?
[Update, I now understand that my approach above is wrong and I have to use a listview builder. CurrentStatus below shows my progress towards achieving that goal.]
Current status:
I have built a list of Widgets:
List<Widget> chatScreenWidgets = [];
And on setState, I am updating that with a new Widget using this:
setState(() { chatScreenWidgets.add(_buildUsersReply("I think there were 35 humans and one horse.")); });
Now at this point, I am not sure how to pass the widget inside the scaffold. I have written some code that does not work. For instance, I tried this:
Code in the image below and in the gist here:
Just for future reference, here is what I really needed to do:
1. Create a list of widgets
List<Widget> chatScreenWidgets = [];
2. Inside my method, I needed to use a setState in order to add elements to that list. Every widget I add to this will be displayed on ths Scaffold.
`setState(() {
chatScreenWidgets.add(_buildUsersReply("Some Text"));
});`
3. And then, load that inside my Scaffold, I used an itemBuilder in order to return a list of widgets to my ListView. I already had that ListView (where I was manually adding children). Now this just returns them through the setState method inside my business logic method (in this case, fetchNewQuestion()).
body: Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 0),
child: new ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 25),
itemCount: chatScreenWidgets.length,
itemBuilder: (BuildContext context, int itemCount) {
return chatScreenWidgets[itemCount];
}
),
),
],
),
);`
I hope this helps future flutter engineers!
forget about the scaffold the idea is about what you really want to change, lets say it is
a list and your getting the data from an array if you update the array, then the list will update,if it is another type widgets then you can handle it in a different way i will edit this answer if you clarify what each part does in your widget as i cant read your full code.
first you have to create an object with two attributes one is the type of the row(if it is a user replay or other messages) and the second attribute is the string or the text.
now create a global list in the listview class from the above object, so you get the data from the user or even as a news and you create a new object from the above class and add your data to it and add it to the list.
item builder returns a widget so according to the the widget that you return the row will be set , so according to the data in the object call one of your functions that return the views like _buildUsersReply(string text).
if you have any other question you can ask :) if this what you need please mark it as the answer.

Flutter- best widget to implement video feature like Instagram

I want to implement feature like Instagram story.
I was wondering what widget comes into play in this situation. What I was thinking is horizontal list view or dismissible. But since each video has to be initialized before playing and I want go to next video or go back to previous video and play it as quick as possible. So, I want to ask what is the possibly best way to do this. Can someone please share your thoughts?
Any help is highly appreciated!
When I started learning flutter, the first app I wrote was this one. The step-by-step of how to create this app is in the flutter website.
This app use the english_words package to generate random Startup names. There's a logic in the middle of the code that load the rest of content when certain quantity of lines is processed.
Widget _buildSuggestions() {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
// The itemBuilder callback is called once per suggested word pairing,
// and places each suggestion into a ListTile row.
// For even rows, the function adds a ListTile row for the word pairing.
// For odd rows, the function adds a Divider widget to visually
// separate the entries. Note that the divider may be difficult
// to see on smaller devices.
itemBuilder: (context, i) {
// Add a one-pixel-high divider widget before each row in theListView.
if (i.isOdd) return Divider();
// The syntax "i ~/ 2" divides i by 2 and returns an integer result.
// For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2.
// This calculates the actual number of word pairings in the ListView,
// minus the divider widgets.
final index = i ~/ 2;
// If you've reached the end of the available word pairings...
if (index >= _suggestions.length) {
// ...then generate 10 more and add them to the suggestions list.
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
}
);
}
It will make the app generate new lines when the user get to end of list. You could maybe take a look at this how-to. You could implement the logic for play/stop video on that part of code.