Part of my Flutter app has a filter function which filters through a list of items. This being part of a UX should indicate the current operation to the user.
The issue I am facing is displaying the current item being filtered (evaluated) - the images simply won't be displayed or will "get stuck" on 1 - reason most likely being the run loop set the state too fast for the redraw to take place.
Here is a rough example of what I am trying to accomplish
import 'dart:async';
import 'package:flutter/material.dart';
void main() async {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int i = 0;
List<String> _images = [
"assets/sample/sample1.jpg",
"assets/sample/sample2.jpg",
"assets/sample/sample3.jpg",
"assets/sample/sample4.jpg",
];
#override
void initState() {
super.initState();
Timer(Duration(), () {
while (true) {
i++;
setState(() {
i = i % 4;
});
}
});
}
#override
Widget build(BuildContext context) {
Widget content = Container(
child: ClipRRect(
clipBehavior: Clip.antiAlias,
child: CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child: Image.asset(_images[i], gaplessPlayback: true,),
),
));
return MaterialApp(
home: Scaffold(
body: content,
),
);
}
Question:
To simplify things, how can I display a few images rapidly (and repeatedly) i.e. 1, 2, 3, 4, 1, 2, 3... with a few milliseconds delay between (at most)?
Don't use while loop because it will block the thread and does not let other tasks be executed
you could use something like Timer.periodic and it gives you the option to control the frame rate that images should change
and also don't use setState because it causes the entire widget to rebuilt you can use ValueNotifer to notify the specific widget direct with changes
and also remember to cancel the timer when your widget gets disposed
here is example
class _PageAState extends State<PageA> {
ValueNotifier<String> currentImage = ValueNotifier("0.png");
int counter=0;
Timer imageTimer;
#override
void initState() {
super.initState();
this.imageTimer=Timer.periodic(Duration(milliseconds: 100), (timer) {
counter++;
currentImage.value="${counter%3}.png";
});
}
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<String>(
valueListenable: currentImage,
builder: (context, value, child) {
return Image.asset(
value,
width: 30,
height: 30,
color: Colors.red,
);
},
);
}
#override
void dispose() {
super.dispose();
if(imageTimer!=null)imageTimer.cancel();
}
}
Related
I would like to create a widget with an animation composed of showing a sequence of images fading in and out with a repeat after complete. Something like this attached animation:
Since I'm new to Flutter, I would like to know what is the best approach to this.
This can be done with AnimatedSwitcher widget. It's one of Flutter's easy-to-use implicit animation widgets. Its main job is to automatically create a cross-fade transition when its child widget changes.
You can see it in action by changing the string below, and do a hot reload. You will see a cross fade transition for 200 ms:
AnimatedSwitcher(
duration: Duration(milliseconds: 200),
child: Text(
'Hello', // manually change the text here, and hot reload
key: UniqueKey(),
),
)
Once you understand how AnimatedSwitcher works, you can decide how to loop through the list images. For simplicity, I'm giving you an example using texts, but the idea is the same.
Full source code:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late final Timer timer;
final values = ['A', 'B', 'C', 'D'];
int _index = 0;
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() => _index++);
});
}
#override
void dispose() {
timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: Center(
child: AnimatedSwitcher(
duration: Duration(milliseconds: 200),
child: Text(
values[_index % values.length],
key: UniqueKey(),
),
),
),
);
}
}
You can do this with AnimationController. After setup animation controller, you just call the repeat function. The animation will be going to a limitless loop. With AnimationStatusListener you can change color and title text.
I got a stateful widget that changes the background image every 10 seconds, I've noticed that it flickers each time it changes the background image.
It cycles through a list of images to use for the background.
When it reaches the end of the list and comes back to load the first image, it will not flicker anymore.
I did some googling on this and the flicker is caused by rebuilding the entire widget in setState.
However, after it loads all the possible widgets with different images, it will not flicker.
My hypothesis is that it will store the previous widget either in cache or will figure out what changed in the previous setState therefore it won't flicker as it knows only to change the background.
I'm not sure if this is right or not.
My question is, how do you load multiple widgets but not show them on the screen and in the background.
So basically during the 10 seconds it takes to switch from the first background image to the second.
There will be a Future that loads all the different possible background images.
Thanks in advance!
EDIT: (Code to replicate issue)
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Title',
theme: ThemeData(
backgroundColor: Colors.white,
primarySwatch: Colors.blue,
),
home: MyHome(),
);
}
}
class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ImageRotater(
child: Text("Some text"),
);
}
}
class ImageRotater extends StatefulWidget {
final Widget child;
ImageRotater({required this.child});
#override
_ImageRotaterState createState() => _ImageRotaterState();
}
class _ImageRotaterState extends State<ImageRotater>
with TickerProviderStateMixin {
late final subtree;
static List<String> imageNames = [
"1.png",
"2.png",
"3.png",
];
int _pos = 0;
late Timer _timer;
late AnimationController _animationController;
#override
void initState() {
subtree = this.widget.child;
//Setting up Animation
_animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 3),
upperBound: 255.0,
lowerBound: 0.0,
value: 255.0,
);
_animationController.reverse();
//Setting up Timer for Casaroul
_timer = Timer.periodic(Duration(seconds: 10), (timer) async {
_animationController.forward();
await Future.delayed(Duration(milliseconds: 3000)).then((_) {
setState(() {
_pos = (_pos + 1) % imageNames.length;
});
_animationController.reverse();
});
});
super.initState();
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return AnimatedBuilder(
animation: _animationController,
builder: (BuildContext context, _) {
return Container(
width: size.width,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/" + imageNames[_pos]),
alignment: Alignment.centerLeft,
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.black.withAlpha(_animationController.value.toInt()),
BlendMode.multiply,
),
),
),
child: subtree,
);
});
}
}
Here is some code,
If you try any 3 images, you will notice the flicker from
Img 1 -> Img 2
Img 2 -> Img 3
HOWEVER FROM,
Img 3 -> Img 1
and onwards, all the transitions will have no flicker.
I think the correct way for reach what you want is use the bloc pattern Bloc library
With this pattern you can separate the state from the ui and you can manage easily state everywhere in your app
So the technical term I was looking for was 'preloading an image'.
I found a post that does exactly what I wanting, it solved the flickering problem.
Here is the link:
Flutter Preloading
After doing an event..I want to show up my Text widget and change that widget when the time is at 23:59:59... so far I have done this code
DateFormat("HH:mm:ss").format(DateTime.now()) =="23:59:59"
? Text("After")
: Text("Before")
but the problem is... whenever I close my app and then re-open app... the widget doesn't change from Text("Before") to Text("After") although the time is already at 23:59:59... the widget only change after I am doing an event click and when app is still opened... is there a way to solve that problem without any additional event?
You have your Texts in build method. This method works only on widget's update, for example, if state changes (that's why widget updates when you do an event click, I think your widget rebuilds after click). But widget doesn't know anything about DateTime.now(). So you should put DateTime.now to state and update it once in second.
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Padding(
padding: EdgeInsets.all(16),
child: BeforeAfter(),
),
),
),
);
}
}
class BeforeAfter extends StatefulWidget {
#override
_BeforeAfterState createState() => _BeforeAfterState();
}
class _BeforeAfterState extends State<BeforeAfter> {
String time = DateFormat("HH:mm:ss").format(DateTime.now()); // our time which we will update
#override
void initState() {
super.initState();
updateTime();
}
void updateTime() {
Future.delayed(Duration(seconds: 1), () {
updateTime();
setState(() {
time = DateFormat("HH:mm:ss").format(DateTime.now()); // update time
});
});
}
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
time == "12:13:00" ? Text("After") : Text("Before"),
Text(time)
]); // compare time with some value
}
}
void main() {
runApp(MyApp());
}
Gif how it works
I'm trying to make a splash screen for my Flutter application. I want my logo to rotate while checking if the user is logged on firebase authentifaction and then going to the views concerned depending of the return value.
The problem is that my application doesn't build properly before my async call (I see my backGround but not the AnimatedBuilder).
I tried running my CheckUser() using the after_layout package, or using this function :
WidgetsBinding.instance.addPostFrameCallback((_) => yourFunction(context));
but it always wait for the CheckUser() function to finish so I don't see the animation as it navigates directly to my other views.
Here's my code if you want to test it :
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:skull_mobile/connexion/login.dart';
import 'accueil.dart';
class SplashPage extends StatefulWidget {
SplashPage({Key key}) : super(key: key);
#override
_SplashPage createState() => _SplashPage();
}
class _SplashPage extends State<SplashPage>
with SingleTickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = new AnimationController(
vsync: this,
duration: new Duration(seconds: 5),
);
animationController.repeat();
checkUser();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[800],
body: Center(
child: Container(
child: new AnimatedBuilder(
animation: animationController,
child: new Container(
height: 150.0,
width: 150.0,
child: new Image.asset('assets/skull.png'),
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * 6.3,
child: _widget,
);
},
),
),
),
);
}
void checkUser() async {
FirebaseAuth.instance.currentUser().then((currentUser) => {
if (currentUser == null)
{Navigator.pushNamed(context, LoginPage.routeName)}
else
{Navigator.pushNamed(context, AccueilPage.routeName)}
});
}
}
Following on my comment I'm sharing here a snippet of my own code and how I handle a splash screen, here called "WaitingScreen", the device's Connection state and then send the user to different pages with different properties depending on the results:
#override
Widget build(BuildContext context) {
switch (authStatus) {
case AuthStatus.notDetermined:
if(_connectionStatus == ConnectionStatus.connected){
return _buildWaitingScreen();
}else{
return _buildNoConnectionScreen();
}
break;
case AuthStatus.notSignedIn:
return LoginPage(
onSignedIn: _signedIn,
setThemePreference: widget.setThemePreference,
);
case AuthStatus.signedIn:
return MainPage(
onSignedOut: _signedOut,
setThemePreference: widget.setThemePreference,
getThemePreference: widget.getThemePreference,
);
}
return null;
}
I have searched the web for auto refresh widgets and auto refresh future JSON for Flutter but the results all seem to be for pull down refresh.
What I need is like a function that I can call and every minute that said function repeats.
I know I have to integrate something like:
var future = new Future.delayed(const Duration(milliseconds: 10), TracksWidget());
However I am not sure where I need to put it.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../model/track.dart';
class TracksWidget extends StatefulWidget {
#override
_TracksWidgetState createState() => _TracksWidgetState();
}
class _TracksWidgetState extends State<TracksWidget> {
Future<Track> track;
#override
Widget build(BuildContext context) {
double c_width = MediaQuery.of(context).size.width;
return new FutureBuilder<Track>(
future: track,
builder: (context, snapshot) {
if (snapshot.hasData) {
Track track = snapshot.data;
return new Container(
padding: const EdgeInsets.all(16.0),
width: c_width,
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.network(track.imageurl, width:200.0, height: 200.0,fit: BoxFit.cover),
Text(track.title),
Text(track.artist),
]),
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
//By default, show a loading spinner.
return CircularProgressIndicator();
},
);
}
#override
void initState() {
super.initState();
track = fetchTrack();
}
Future<Track> fetchTrack() async {
final response =
await http.get('http://139.59.108.222:2199/rpc/drn1/streaminfo.get');
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON.
var responseJson = json.decode(response.body);
// assume there is only one track to display
// SO question mentioned 'display current track'
var track = responseJson['data']
.map((musicFileJson) => Track.fromJson(musicFileJson['track']))
.first;
return track;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
}
The simplest way to do this I have found is to use the Timer function.
If you put the timer into initState it will start when the the app is started.
In the code below, the timer will call the addValue() method every 5 seconds which increases the value by one each time. Just remember to dispose of the timer when you have finished with it.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Timer timer;
int counter = 0;
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 5), (Timer t) => addValue());
}
void addValue() {
setState(() {
counter++;
});
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(counter.toString())
],
),
),
);
}
}