Noticeable delay when changing an image dynamically in Flutter - flutter

I am new to Flutter and experienced a problem while trying to make a mobile application.
When switching the image being displayed there is a noticeable delay, where nothing is shown, between the action and the new image appearing.
Adding gaplessPlayback: true to the Image widget does prevent the original image disappearing until the other is loaded but the delay remains.
I tried to precache both images but noticed no difference in load time. Is this delay to be expected or am I doing something wrong?
Issue without gaplessPlayback
class Wallpaper extends StatefulWidget {
#override
StateWallpaper createState() => StateWallpaper();
}
class StateWallpaper extends State<Wallpaper> {
late AssetImage walPath;
late AssetImage wal1;
late AssetImage wal2;
#override
void initState() {
wal1 = AssetImage('assets/wal_dark.jpg');
wal2 = AssetImage('assets/wal_light.jpg');
walPath = wal1; //Set default wallpaper
super.initState();
}
#override
void didChangeDependencies() {
precacheImage(wal1, context);
precacheImage(wal2, context);
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Column(children: [
FloatingActionButton(onPressed: () {
setState(() {
if (walPath == wal2) {
walPath = wal1;
} else {
walPath = wal2;
}
});
}),
Image(image: walPath, gaplessPlayback: true)
]);
}
}

How big are your images (width/height in pixels, and file size)?
Perhaps they are too large, and they are being resized before being displayed?
Try with images that are close to the pixel width/height of the device and see if it is faster...
Also, is it a release build?

Related

Where to put Flutter AJAX?

I come from the web dev world. I have designed a Flutter app that needs to grab some JSON from the web very early on. I want my first screen to show up, and while it is being drawn, behind the scenes I want the JSON fetch to happen. There is a start button on Page 1 that will be disabled until the JSON is fetched. (But Page 1 will have some text info to keep the reader engaged until the fetch happens.)
Where would I stick the JSON fetch? Can I put it in initState of Page 1? Or can I initiate a call at the very start of main at the root of the app? Or somewhere else?
FWIW I use Provider for state management, if that helps?
Thanks a ton!
Calling it in the initState is best since your widgets still build. Since there will be a state change, make sure you use a stateful widget. Also, don't forget to call setState to re-enable the button. Example code:
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
#override
State<FirstPage> createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
void initState() {
super.initState();
jsonFetch();
}
void jsonFetch() {
// your functionality here
// on complete call:
/*
setState(() {
isJsonFetched = true;
});
*/
}
void doSomethingOnPressed(){
// Your functionality here
}
bool isJsonFetched = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: ElevatedButton(
onPressed: (){
isJsonFetched
? doSomethingOnPressed()
:null;
},
child: Text('Press Me')
)
)
);
}
}

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

Flutter - rebuild GestureDetector widget without user interaction

I am trying to display 3 images one after another by using GestureDetector and its onTap method but one image should be displayed without tapping. Supposing I have three images 1.png, 2.png, 3.png I would like to display the first image 1.png at the first place. Clicking on it will result in displaying the second image 2.png.
The second image 2.png should now be replaced after x seconds by displaying the image 3.png.
I am using a stateful widget that stores which image is currently shown. For the first image change the onTap will execute setState( () {screen = "2.png"} and the widget is rebuilt. However, I am pretty stuck now when the third image should be displayed after x seconds delay because there should not be any user interaction and I am not sure where to change it. I tried a lot of stuff especially editting the state from outside the widget but I did not succeed til now.
This is the code I am using:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Navigation Basics',
home: MyScreen(),
));
}
class MyScreen extends StatefulWidget {
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
String currentScreen = 'assets/images/1.png';
#override
Widget build(BuildContext context) {
return GestureDetector(
child: FittedBox(
child: Container(
child: Image.asset(currentScreen),
),
fit: BoxFit.fill,
),
onTap: () {
if (currentScreen == 'assets/images/1.png') {
setState(() {
currentScreen = 'assets/images/2.png';
});
}
},
);
}
}
Displaying the second image worked without any issues but I have no idea what/where to code in order to display the third image.
Thanks for any help!
I'm assuming you don't want the second and third picture to respond to the gesture detection. Try this, please.
onTap: () {
if (currentScreen == 'assets/images/1.png') {
setState(() {
currentScreen = 'assets/images/2.png';
});
Future.delayed(const Duration(milliseconds: 3000), () {
setState(() {
currentScreen = 'assets/images/3.png';
});
});
} else {
return;
}
},

How to display loading widget until the main widget is loaded in flutter?

I have a widget with a lot of contents like image, text and more, which make it heavy widget in flutter app, But when the app is navigated to the widget having the complex widget the app faces the jank since the widget is too large to load at an instant,
I want to show simple lite loading widget until the original widget is loaded thus removing the jank from the app and enable lazy loading of the widget,
How to achieve this in flutter?
EDIT:-
To make it clear, I am not loading any data from the Internet, and this is not causing the delay. For Loading the data from Internet we have FutureBuilder. Here my widget is itself heavy such that it takes some time to load.
How to display loading Widget while the main widget is being loaded.
First you have to create a variable to keep the state
bool isLoading = true; //this can be declared outside the class
then you can return the loading widget or any other widget according to this variable
return isLoading ?
CircularProgressIndicator() //loading widget goes here
: Scaffold() //otherwidget goes here
you can change between these two states using setState method
Once your data is loaded use the below code
setState(() {
isLoading = false;
});
Sample Code
class SampleClass extends StatefulWidget {
SampleClass({Key key}) : super(key: key);
#override
_SampleClassState createState() => _SampleClassState();
}
bool isLoading = true; // variable to check state
class _SampleClassState extends State<SampleClass> {
loadData() {
//somecode to load data
setState(() {
isLoading = false;//setting state to false after data loaded
});
}
#override
void initState() {
loadData(); //call load data on start
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
child: isLoading ? //check loadind status
CircularProgressIndicator() //if true
:Container(), //if false
);
}
}
This is a perfect place to use a FutureBuilder.
Widget loadingWidget = ...;
Future<Widget> buildHeavyWidget() async {
// build and return heavy widget
}
FutureBuilder(
future: buildHeavyWidget(),
builder: (context, snapshot) {
if(snapshot.hasData) {
// after the future is completed
// the heavy widget is availabe as snapshot.data
return snapshot.data;
}
return loadingWidget;
},
)
First define a bool value.
bool isLoading = false;
In your function.
yourfunction(){
setState(){
isLoading = true;
}
setState(){
isLoading = false;
}
}
In your widget.
isLoading?CircularProgressIndicator():Widget()

Listening For Device Orientation Changes In Flutter

I am looking for a way of listening for changes to the phones orientation, with the intent to hide something if the phone is Landscape.
My Layout is currently only displayed in portrait, as intended, but I want my app to do something if the device is rotated to Landscape, while keeping the layout in Portrait.
I have tried using a OrientationBuilder, but this only works if the layout changes to Landscape.
I have also tried using MediaQuery.of(context).orientation, but it continues to return portrait once the device is rotated, again only using the layouts orientation.
You can listen to screen size changes, but MediaQuery.of(...) should work as well and should cause rebuilds of your widget when orientation changes
https://stephenmann.io/post/listening-to-device-rotations-in-flutter/
import 'dart:ui';
import 'package:flutter/material.dart';
class WidthHeight extends StatefulWidget {
WidthHeight({ Key key }) : super(key: key);
#override
WidthHeightState createState() => new WidthHeightState();
}
class WidthHeightState extends State
with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
double width = 0.0;
double height = 0.0;
#override void didChangeMetrics() {
setState(() {
width = window.physicalSize.width;
height = window.physicalSize.height;
});
}
#override
Widget build(BuildContext context) {
return new Text('Width: $width, Height $height');
}
}
Using MediaQuery directly in didChangeMetrics() returns previous values. To get the latest values after orientation change. Use WidgetsBinding.instance.addPostFrameCallback() inside it.
https://github.com/flutter/flutter/issues/60899
class OrientationSample extends StatefulWidget {
const OrientationSample({Key? key}) : super(key: key);
#override
_OrientationSampleState createState() => _OrientationSampleState();
}
class _OrientationSampleState extends State<OrientationSample> with WidgetsBindingObserver {
Orientation? _currentOrientation;
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
#override
void didChangeMetrics() {
_currentOrientation = MediaQuery.of(context).orientation;
print('Before Orientation Change: $_currentOrientation');
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
setState(() {
_currentOrientation = MediaQuery.of(context).orientation;
});
print('After Orientation Change: $_currentOrientation');
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _currentOrientation != null ? Text('$_currentOrientation') : Text('Rotate Device to Test'),
),
);
}
}
You can wrap your widget with visibility and set the opacity parameter to getOpacityForOrientation() and in your Screen you can add the function:
double getOpacityForOrientation() {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
return 0;
} else {
return 1;
}
}
when the orientation changes the widget will rebuild and the opacity will change and hide/show
Both OrientationBuilder and MediaQuery.of(context).orientation should be able to get the job done. But you're saying that the device's orientation never changes which makes me think that you have not enabled auto-rotate in your device.
Can you enable auto-rotate from quick settings and give it a try?
Using provider and OrientationBuilder:
orientation_provider.dart
import 'package:flutter/material.dart';
class OrientationProvider extends ChangeNotifier {
Orientation _orientation = Orientation.portrait;
Orientation get getOrientation {
return _orientation;
}
void changeOrientation(Orientation newOrientation) {
print("CHANGE ORIENTATION CALLED: old: $_orientation, new: $newOrientation");
bool hasChanged = _orientation != newOrientation;
_orientation = newOrientation;
if(hasChanged) notifyListeners();
}
}
In parent widget, use OrientationBuilder and set orientation in provider
.
.
.
child: OrientationBuilder(
builder: (context, orientation){
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
context.read<OrientationProvider>().changeOrientation(orientation);
});
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark().copyWith(
textTheme: ThemeData.dark().textTheme.apply(
fontFamily: 'Nunito',
),
.
.
.
Where orientation change need to be listened
child: context.watch<OrientationProvider>().getOrientation == Orientation.portrait ? WIDGET_PORTRAIT
: WIDGET_LANDSCAPE
So Im posting this here to try to help maybe a little, but I will be honest in that it feels like a hack. The problem that Matthew is describing is -- if you specifically lock your app to portrait mode at compile time, MediaQuery and OrientationBuilder don't help because they are never triggered. In my case, I have an app which is locked to portrait, but I am trying to add one screen that streams video, which I would like to play full screen when the phone is rotated. As above, because it is locked at compile time MediaQuery and OrientationBuilder won't work.
For my 'hack' in my screen controller, I listen to a stream subscription from the accelerometer events API. If the stream event.y is < 3, then the phone is close to being horizontal, I can then use this to change an int that controls the number of rotations for an Expanded rotating box that houses the video player.
This does not work for working out if it is right or left-handed rotation... and as I say, it feels like a hack, but it's a start...
just using code like this, if you wanna detect orientation of device
String type_orien = "potrait";
Future<void> detect_change_orientation() async {
await Future.delayed(const Duration(seconds: 1), () async {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
if (type_orien ==
"potrait") // if orien change and before is potrait then reload again
{
print("landscape ss");
await reload_set_scren();
type_orien = "landscape";
}
} else {
if (type_orien ==
"landscape") // if orien change and before is landscape then reload again
{
print("potrait ss");
await reload_set_scren();
type_orien = "potrait";
}
}
});
}
Future<void> reload_set_scren() {
//... do whats ever u want
}
#override
Widget build(BuildContext context) {
detect_change_orientation(); // call function here <--
return Scaffold(
);
}