Flutter How to update widgets one after the other instead of at the same time - flutter

I want the function update_list to change the values in the list when it is called. And then I want it to change the text in the column.
That works, but now I want the first value to change (and to see the new value on the screen) and then a second later the next value and so on. But with my code it changes all together at the end.
How can I make the values change one after the other and not all at once?
class class_one extends StatefulWidget {
const class_one({Key? key}) : super(key: key);
#override
State<class_one> createState() => _class_oneState();
}
class _class_oneState extends State<class_one> {
List list = [0,1,2,3];
void update_list(){
setState((){
for (var i = 0; i < 4; i++){
list[i]=list[i]+10;
sleep(Duration(seconds: 1));
}
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text('${list[0]}'),
Text('${list[1]}'),
Text('${list[2]}'),
Text('${list[3]}'),
GestureDetector(onTap: (){update_list();},child: Container(color: Colors.red,child: Text("next"),),)
],
),
);
}
}

you can use two solution for this issue:
1: use streaming mechanism(actually use from streamBuilder widget).
2:
void update_list() async{
for (var i = 0; i < 4; i++){
setState((){
list[i]=list[i]+10;
});
await Future.delayed(Duration(seconds: 1));
}
}

Related

Flutter Provider: provided data seems to get reset independently without me resetting it

The minimal reproducible code below aims to update a variable provided by the Provider inside a ChangeNotifierProvider but a strange behavior happen. Let's say I have a variable called _counterValue. All I'm doing is, initializing this variable by 0 and then updating it's value to 20 after my Build is finished. I'm also logging it's value each time I'm getting/fetching it. this is the result:
[log] Getting Counter Value 0
[log] Getting Counter Value 20
[log] Getting Counter Value 20
[log] Getting Counter Value 0
Here we notice that the getter was called 4 times and the value of the counter was reset dependently. Further more, I created a local variable and updated it inside the initState also and it kept it's value. I'm confused what is my mistake here?
Code Snippet:
Screen:
class CounterScreen extends StatefulWidget {
const CounterScreen({Key? key}) : super(key: key);
#override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int localData = 0;
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
bind();
});
}
Future<void> bind() async {
Provider.of<CounterProvider>(context, listen: false).updateCounterValue();
localData = 20;
setState(() {
});
}
#override
Widget build(BuildContext context) {
log("Build");
// return Container();
return SafeArea(
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Provider Data " + context.watch<CounterProvider>().counterValue().toString()),
Text("Local Data " + localData.toString()),
],
),
),
),
);
}
}
Provider:
class CounterProvider extends ChangeNotifier {
CounterProvider();
int _counterValue = 0;
int counterValue(){
log("Getting Counter Value $_counterValue");
return _counterValue;
}
void updateCounterValue(){
_counterValue = 20;
notifyListeners();
}
}
Thanks for your help.

Flutter Rive - animation doesn't change when changing Number input in state machine

I tried to play with boolean input and it worked really nice, but changing number input won't change the animation at all.
I made sure couple of times animation is right, state machine name is correct and input name is correct as well.
I tried changing SMIInput<num>? _hoursInput; into SMIInput<SMINumber>? _hoursInput; and than in casting integer as SMINumber like so: _hoursInput?.value = 100 as SMINumber
I tried using double instead of SMINumber. I tried assigning value with setState and without setState.
I made sure the State Machine Animations are in fact depended on the number value.
Nothing works.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:rive/rive.dart';
/// An example showing how to drive two boolean state machine inputs.
class Eldey extends StatefulWidget {
final Map data;
const Eldey({Key? key, required this.data}) : super(key: key);
#override
_EldeyState createState() => _EldeyState();
}
class _EldeyState extends State<Eldey> {
/// Tracks if the animation is playing by whether controller is running.
bool get isPlaying => _controller?.isActive ?? false;
Artboard? _riveArtboard;
StateMachineController? _controller;
SMIInput<num>? _hoursInput;
#override
void initState() {
super.initState();
// Load the animation file from the bundle, note that you could also
// download this. The RiveFile just expects a list of bytes.
rootBundle.load('assets/animations/testing_3.riv').then(
(data) async {
// Load the RiveFile from the binary data.
final file = RiveFile.import(data);
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard;
var controller = StateMachineController.fromArtboard(artboard, 'State Machine');
if (controller != null) {
artboard.addController(controller);
_hoursInput = controller.findInput('Hours');
}
setState(() => _riveArtboard = artboard);
},
);
}
#override
Widget build(BuildContext context) {
int time = int.parse(widget.data['ftime'].substring(0,2));
if(time > 9 && time < 17){
setState(() {
_hoursInput?.value = 100.0;
});
}
return Expanded(
child: Center(
child: _riveArtboard == null
? const SizedBox()
: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Rive(
artboard: _riveArtboard!,
),
),
)
);
}
}

State and Scroll position restore Flutter

I have an app which fetches posts from a site using a API and then displays it. There are three navigation options, which are basically filters.
The problem is, whenever I switch to another navigation tab (I'm using bottom navigation bar), it ends up rebuilding the whole page, meaning it will fetch all that data again and it might potentially contain new data.
What I want to do is to keep restore this data in a way that is fast and my initState() doesn't get called(because that is what fetches the data). I did try using all the different kind of keys but I cant get it to work.
Main page:
class AppHomePage extends StatefulWidget {
AppHomePage({Key? key}) : super(key: key);
#override
_AppHomePageState createState() => _AppHomePageState();
}
List<Widget> _navs = [
BestPostsRoute(key: PageStorageKey("bestP")),
HotPostsRoute(key: PageStorageKey("hotP")),
NewPostsRoute(key: PageStorageKey("newP"))
];
class _AppHomePageState extends State<AppHomePage> {
int _currentIndex = 0;
onTap(index) => {
setState(() => {_currentIndex = index})
};
#override
Widget build(BuildContext context) {
return Scaffold(
/* appbar ... */
body: _navs.elementAt(_currentIndex),
bottomNavigationBar: BottomNavigationBar(
items: [
/* nav items */
],
currentIndex: _currentIndex,
onTap: onTap,
),
);
}
}
One of the three pages(the code is similar in all three):
/* imports... */
class HotPostsRoute extends StatefulWidget {
HotPostsRoute({Key? key}) : super(key: key);
#override
_HotPostsRouteState createState() => _HotPostsRouteState();
}
class _HotPostsRouteState extends State<HotPostsRoute> {
late PostInstance postInstance;
List<Post> _posts = [];
bool _loaded = false;
fetchPosts(String? after) async {
var stream = postInstance.front.hot(limit: 10, after: after);
await for (UserContent post in stream) {
Submission submission = post as Submission;
Post pPost = Post(submission);
pPost.parse().then((value) => setState(() {
_posts.add(pPost);
}));
}
setState(() {
_loaded = true;
});
}
#override
void initState() {
super.initState();
if (mounted) {
setState(() {
redditInstance =
Provider.of<PostInstanceState>(context, listen: false)
.getInstance;
});
fetchPosts("");
}
}
// Fetches and generates posts
Widget _buildPosts() {
return ListView.builder(
itemCount: _posts.length + 1,
itemBuilder: (ctx, index) {
if (index < _posts.length) {
return _buildPost(_posts.elementAt(index));
} else {
fetchPosts(_posts.last.fullname);
return SpinKitDualRing(color: Colors.white);
}
},
);
}
// A singular post
Widget _buildPost(Post post) {
print(post.object);
return PostCard(post, key: ObjectKey(post.object)); // .object just creates a map of all fields
}
#override
Widget build(BuildContext context) {
setState(() {});
return Container(
child: _loaded ? _buildPosts() : SpinKitDualRing(color: Colors.white),
);
}
}
So I kept searching and eventually a post on Medium led me to the IndexedStack Widget.
Its a widget that is made from the Stack widget and basically loads and stores the state of all its childrens. Unlike Stack, it shows its children one at a time and thus is perfect to use with BottomNavigationBar.
Here's the Blog post for anyone looking out.

Flutter: How to share an instance of statefull widget?

I have a "WidgetBackGround" statefullwidget that return an animated background for my app,
I use it like this :
Scaffold( resizeToAvoidBottomInset: false, body: WidgetBackGround( child: Container(),),)
The problem is when I use navigator to change screen and reuse WidgetBackGround an other instance is created and the animation is not a the same state that previous screen.
I want to have the same animated background on all my app, is it possible to instance it one time and then just reuse it ?
WidgetBackGround.dart look like this:
final Widget child;
WidgetBackGround({this.child = const SizedBox.expand()});
#override
_WidgetBackGroundState createState() => _WidgetBackGroundState();
}
class _WidgetBackGroundState extends State<WidgetBackGround> {
double iter = 0.0;
#override
void initState() {
Future.delayed(Duration(seconds: 1)).then((value) async {
for (int i = 0; i < 2000000; i++) {
setState(() {
iter = iter + 0.000001;
});
await Future.delayed(Duration(milliseconds: 50));
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return CustomPaint(painter: SpaceBackGround(iter), child: widget.child);
}
}
this is not a solution, but maybe a valid workaround:
try making the iter a static variable,
this of course won't preserve the state of WidgetBackGround but will let the animation continue from its last value in the previous screen
A valid solution (not sure if it's the best out there):
is to use some dependency injection tool (for example get_it) and provide your WidgetBackGround object as a singleton for every scaffold in your app

How to test the order of multiple list items in Flutter / Dart?

Intro
We have a ListView named OrganizationList which contains a ListTile widget for every item. We use a Text widget to display the name of the organization inside the ListTile.
We would like to test if the organization name is displayed correctly and in the correct order.
Our current solution
We have the following assertions:
var organizationA = find
.descendant(
of: find.byType(OrganizationList),
matching: find.byKey(
Key('0'),
),
)
.evaluate()
.first
.widget as Text;
expect(textOrganization.data, 'organization-a');
var organizationB = find
.descendant(
of: find.byType(OrganizationList),
matching: find.byKey(
Key('1'),
),
)
.evaluate()
.first
.widget as Text;
expect(textOrganization.data, 'organization-b');
This feels like a very cumbersome way of testing if the right label is shown for the list items. But I fail to find a more elegant way.
Question
What is a more elegant way in Flutter / Dart to assert both the content of a list item and the order of all items?
With my solution you can check if a ListItemWidget at specific position and text is visible(or not):
void checkPosition(int index, SomeObj obj, {bool isOnPosition = true}) {
final listItemFinder = find.byType(ListItemWidget);
final listItem = listItemFinder.evaluate().isEmpty
? null
: listItemFinder.at(index);
if (listItem == null) {
if (isOnPosition) {
fail('List not found');
}
return;
}
final positionText = find.text(obj.text);
expect(find.descendant(of: listItem, matching: positionText),
isOnPosition ? findsOneWidget : findsNothing);
}
The easiest way to get elements from List and verify is widgetList, which gives a list of elements of whatever data type we have provided.
Sample test:
Problem: Verify the second item from List
const List<String> listString = [
"Ac",
"Fuel Sensor",
"Power",
"Panic",
"Camera",
"Relay",
"Duty Button",
"Other"
];
Test:
testWidgets('Listview item verify', (WidgetTester tester) async {
await tester.pumpWidget(ListViewApp());
/// Verify OrganizationIte sequence to get and verify title.
OrganizationItem item = tester
.widgetList<OrganizationItem>(find.byType(OrganizationItem))
.elementAt(2);
String data = item.title;
expect("Power", data);
});
App Code:
class ListViewApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Checked Listview',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: MyHomePage(title: 'Flutter Checked Listview'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> _list = [];
#override
void initState() {
setState(() {
for (int i = 0; i < listString.length; i++) {
_list.add('${listString[i]}');
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Listview Sample'),
),
body: ListView.builder(
itemBuilder: (_, int index) {
return OrganizationItem(
title: _list[index],
);
},
itemCount: _list.length,
),
);
}
}
class OrganizationItem extends StatelessWidget {
final String title;
const OrganizationItem({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(this.title),
);
}
}
Try a for loop:
// This is for saving a reference to the ListTile from the previous iteration
ListTile? previousListTile;
for (int j = 0; j < aListOfModels.length; j++) {
final organizationA = find.descendant(
of: find.byType(OrganizationList),
matching: find.byKey(
Key('0'),
),
);
// This is important to show the invisible late elements
await widgetTester.scrollUntilVisible(organizationA, tryToFindTheSuitableDelta);
// I suppose that organizationA finds a ListTile widget
ListTile currentListTile = widgetTester.widget<ListTile>(organizationA);
/*
Make your expectations about the content as you like
....
...
*/
// To expect about the order, do the following:
final listTileWidgetList = widgetTester
.widgetList<ListTile>(find.byType(ListTile))
.toList();
if (j > 0) {
// Expect that the currentListTile is the first next ListTile of the previousListTile
expect(listTileWidgetList.indexOf(currentListTile),
equals(listTileWidgetList.indexOf(previousListTile!) + 1));
}
// To allow you to make your expectation about the order in the next iteration
previousListTile = currentListTile;
}