Problem: One of screen in my app has bunch of network images that are being fetched from an API. When user navigates to that screen some images load faster than others, hence users sees a screen that is not fully loaded.
Expected behaviour: I want to show a CircularProgressIndicator until all the network images are fully loaded.
P.S. Below code doesn't do what I wanted, as it executes the function while images are still loading.
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => yourFunction(context));
}
Also I am using SvgPicture.network from flutter_svg package.
final Widget networkSvg = SvgPicture.network(
'https://site-that-takes-a-while.com/image.svg',
semanticsLabel: 'A shark?!',
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator()),
);
Maybe this could help Flutter image preload
Related
In total I have three files: Home.dart, Post.dart and PostBuilder.dart
In Home.dart I have a listView.seperated:
List post = [];
void initState() {
super.initState();
getPost();
}
Future getPost()async
{
//function that gets and paginates the data from the backend and adds it to the list.
}
body: ListView.separated(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: post.length,
itemBuilder: (context, index) {
return SizedBox(
width: double.infinity,
child: PostBuilder(post[index]);
},
separatorBuilder: (context, index) {
return const Divider();
}),
And this code on my PostBuilder.dart file:
class PostBuilder extends StatefulWidget {
final post;
const PostBuilder(this.post, {super.key});
#override
State<PostBuilder> createState() => _PostBuilderState();
}
class _PostBuilderState extends State<PostBuilder> {
return Container(child: CachedNetworkImage(
imageUrl: widget.post['postUrl'],
fit: BoxFit.contain,
),
),
Now the issue I'm facing is that whenever I navigate back from Post.dart screen to the home screen, since the height of the cached network image is dynamic, it rebuilds and it seems like a glitch.
Is there any way to stop this behaviour? Maybe something like saving the state of the screen when navigating, which might prevent reloading/rebuilding the list and hence everything stays as it was.
Also I did try the following things:
Adding listview.separated with cacheExtent.
Adding listview.separated in a parent SingleChildListView widget.
Adding 'AutomaticKeepAliveClientMixin' to the class
Unfortunately, none of this worked.
After a couple of days of experimentation, I ended up creating my own "image cacher" (similar to my video cacher) that got me the height and width of the image and used that to set the container size of the list which solve the "glitchiness" when the Image would reload after navigating back.
Here is how I did it if anyone is still looking for the answer:
I used a package called image_pixels
When I got the image information I convert it into flutter logical pixels by simply using this formula:
For getting height: MediaQuery.of(context).width/ (image.width/image.height)
For getting width: MediaQuery.of(context).height/(image.width/image.height)
What it does is first it gets the aspect ratio of the image and then that ratio is used to determine the height of the container to preserve the intrinsic aspect ratio of the image.
Since child size is determined by the Parent if you have a dynamic image you will have to get its size and use it to determine the parent. Or else you will have this glitchy list behaviour as it only builds visible widgets and if it is dynamic, then as it rebuilds the child from scratch it will resize the list.
Hope this helps to anyone looking for an answer.
I have two pages, Page A and Page B.
To do transition from Page A to Page B I use Navigation.push():
Navigator.push(
context,
CupertinoPageRoute(...)
);
However, this transition has so much jank and frame drops. (yes, I run in Profile mode)
One reason I think of is Page B has so much heavy-duty UI rendering (such as Google Maps and Charts) and I also noticed as page slide animation is happening, Page B rendering has already begun.
I'm trying to understand how I can improve this experience and maybe somehow pre-load Page B.
I already read this suggestion from a Github issue (tldr use Future.microtask(()) but it didn't work for me. Would appreciate any help or suggestion.
If the screen transition animation is especially janky the first time you run the app, but then gets smoother if you run the transition back and forth a few times, this is a known problem that both the Flutter team and their counterparts within Android and iOS are working on. They have a suggestion for a workaround here: https://docs.flutter.dev/perf/shader , built on "warming up shaders", but since it only warms up the shaders on the specific device you're debugging on, I honestly feel it's like the programming equivalent of "sweeping it under the rug"... You won't see the problem on your device anymore, but it's still there on other devices!
I however found a workaround for this problem myself, which works surprisingly well! I actually push the janky page, wait a bit, and then pop it again, without the user knowing it! 🙂 Like this:
import 'login_screen.dart';
import 'register_screen.dart';
import 'home_screen.dart';
import 'loading_screen.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
class WelcomeScreen extends StatefulWidget {
const WelcomeScreen(Key? key) : super(key: key);
#override
_WelcomeScreenState createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen> {
#override
void initState() {
super.initState();
warmUp();
}
Future warmUp() async {
// This silly function is needed to remove jank from the first run screen transition...
print('Running warmUp()');
await Firebase.initializeApp();
// If not using Firebase, you'll have to add some other delay here!
// Otherwise, you will get errors below for trying to push new screens
// while the first one is still building.
if (mounted) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LoginScreen(popWhenDone: false)));
Navigator.push(context, MaterialPageRoute(builder: (context) => RegisterScreen(popWhenDone: false, userType: UserType.artist)));
Navigator.push(context, MaterialPageRoute(builder: (context) => HomeScreen()));
Navigator.push(context, MaterialPageRoute(builder: (context) => LoadingScreen())); // Shows a spinner
await Future.delayed(Duration(milliseconds: 1000));
if (mounted) {
Navigator.popUntil(context, (route) => route.isFirst);
}
}
}
#override
Widget build(BuildContext context) {
print('Building $runtimeType');
return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MaterialButton(
child: const Text('Sign Up'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const RegisterScreen();
}));
},
),
MaterialButton(
child: const Text('Log In'),
onPressed: () async {
await Firebase.initializeApp(); // In case I remove the warmUp() later...
if (mounted) {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, a1, a2) {
return LoginScreen();
},
transitionsBuilder: (context, a1, a2, child) {
return child; // I want only a Hero animation between the screens, nothing else
},
transitionDuration: const Duration(milliseconds: 1000),
),
);
}
},
),
],
),
Expanded(
flex: 4,
child: Hero(tag: 'logoWText', child: Image(image: AssetImage(kImageLogoWText))),
),
],
),
),
);
}
}
This ads a second of waiting for the app to load, with a spinner showing, but I find that most times, the spinner barely has time to show anyway, and in any case, it is a much better user experience to wait a sec for the app to load than to experience jank during use!
Other tips
If your screen transitions are still janky, even if you have run them back and forth, then you probably need to trim the performance of the screens involved. Perhaps it is your build method that's too big, and has too many things going on in it? Maybe certain widgets get rebuilt many times during each build of the screen?
Check out these pieces of advice and see if it helps: https://docs.flutter.dev/perf/best-practices In any case, they should improve your app's performance in general. 🙂
Edit: And check out this link, as provided by ch271828n in a comment below: https://github.com/fzyzcjy/flutter_smooth
If you are having jank without the shader compilation problem, then there is indeed a (new) approach to make it ~60FPS smooth without changing your code (indeed, only need to add 6 characters - CupertinoPageRoute -> SmoothCupertinoPageRoute).
GitHub homepage: https://github.com/fzyzcjy/flutter_smooth
Indeed, I personally see such jank (i.e. jank not caused by shader compilation) a lot in my app, maybe because the new page is quite complicated.
Disclaimer: I wrote that package ;)
Try adding a small delay before loading the initial tasks in page B. Maybe with a Future.delayed()
I am trying to use RefreshIndicator in my flutter app so i use this builtin library:
child: SafeArea(
child: RefreshIndicator(
onRefresh: () => _onRefreshData(),
child: Column(
children: <Widget>[
Expanded(...
In my page i have 2 list.when this page appear I shows a dialog and I get data from server and show these data inside of lists:
Future<void> _onRefreshData() async {
getMyChan();
}
void getMyChan() async {
Future.delayed(Duration.zero, () => _showProgressDialog());
_myChannel = await MyToolsProvider().getMe(_testToken);
getTools();
setState(() {
_closeDialog();
});
}
Now i want to use RefreshIndicator to refresh my lists but i have a question:
I just want to use swap of RefreshIndicator and don't need circle progress because as you can see i am using progressDialog in getMyChan() method so i do not need circle progress.
How can i hide circle progress inside RefreshIndicator?
Unfortunately (or not), the RefreshIndicator indicator don't have an option to hide the RefreshProgressIndicator widget present inside.
The only way is to copy the Widget in your project and replace the RefreshProgressIndicator with an empty Container here :
https://github.com/flutter/flutter/blob/f3d95cd734ad23b7f9e15e7d0bc182d40965e05f/packages/flutter/lib/src/material/refresh_indicator.dart#L459
I need to add the overlay above the camera like rectangle box and need to save the image to gallery
Tried the basic flutter camera exampleenter image description here
If you are not too worried about limiting the image resolution to the phone's display resolution, you can opt to automatically take a screenshot of the display and save it.
For this to work, you'll need Flutter native_screenshot package.
Capturing the screenshot:
Future<void> takeScreenShot() async{
String path = await NativeScreenshot.takeScreenshot();
print(path);
}
The image will be saved in the folder indicated by the path.
Since the function captures the whole screen, you can use a Stack() widget to display your camera preview and the overlay on top of each other. A simple build would look like,
#override
Widget build(BuildContext context) {
if (controller == null || !controller.value.isInitialized) {
return Container();
}
return OverflowBox(
child: Stack(
children: <Widget>[
CameraPreview(controller),
Container(),
],
),
);
}
In the above code you'll need to initialize the camera controller in order to display the CamerarPreview(). You can edit the Container() child widget (or anything similar to that) to create your overlay box.
In android version, Flutter TextEditingController does not scroll above keyboard like default text fields do when you start typing in field. I tried to look in sample apps provided in flutter example directory, but even there are no example of TextEditController with such behaviour.
Is there any way to implement this.
Thanks in advance.
so simple
if your textfields is between 5-10 fields
SingleChildScrollView(
reverse: true, // add this line in scroll view
child: ...
)
(August 20, 2021 Flutter 2.2.3)
I think my answer might be the cleanest solution for this problem:
#override
Widget build(BuildContext context) {
/// Get the [BuildContext] of the currently-focused
/// input field anywhere in the entire widget tree.
final focusedCtx = FocusManager.instance.primaryFocus!.context;
/// If u call [ensureVisible] while the keyboard is moving up
/// (the keyboard's display animation does not yet finish), this
/// will not work. U have to wait for the keyboard to be fully visible
Future.delayed(const Duration(milliseconds: 400))
.then((_) => Scrollable.ensureVisible(
focusedCtx!,
duration: const Duration(milliseconds: 200),
curve: Curves.easeIn,
));
/// [return] a [Column] of [TextField]s here...
}
Every time the keyboard kicks in or disappears, the Flutter framework will automatically call the build() method for u. U can try to place a breakpoint in the IDE to figure out this behavior yourself.
Future.delayed() will first immediately return a pending Future that will complete successfully after 400 milliseconds. Whenever the Dart runtime see a Future, it will enter a non-blocking I/O time (= inactive time = async time = pending time, meaning that the CPU is idle waiting for something to complete). While Dart runtime is waiting for this Future to complete, it will proceed to the lines of code below to build a column of text fields. And when the Future is complete, it will immediately jump back to the line of code of this Future and execute .then() method.
More about asynchronous programming from this simple visualization about non-blocking I/O and from the Flutter team.
Flutter does not have such thing by default.
Add your TextField in a ListView.
create ScrollController and assign it to the ListView's controller.
When you select the TextField, scroll the ListView using:
controller.jumpTo(value);
or if you wish to to have scrolling animation:
controller.animateTo(offset, duration: null, curve: null);
EDIT: Of course the duration and curve won't be null. I just copied and pasted it here.
Thank you all for the helpful answers #user2785693 pointed in the right direction.
I found complete working solution here:
here
Issue with just using scroll or focusNode.listner is, it was working only if I focus on textbox for the first time, but if I minimize the keyboard and again click on same text box which already had focus, the listner callback was not firing, so the auto scroll code was not running. Solution was to add "WidgetsBindingObserver" to state class and override "didChangeMetrics" function.
Hope this helps others to make Flutter forms more user friendly.
This is an attempt to provide a complete answer which combines information on how to detect the focus from this StackOverflow post with information on how to scroll from Arnold Parge.
I have only been using Flutter for a couple days so this might not be the best example of how to create a page or the input widget.
The link to the gist provided in the other post also looks like a more robust solution but I haven't tried it yet. The code below definitely works in my small test project.
import 'package:flutter/material.dart';
class MyPage extends StatefulWidget {
#override createState() => new MyPageState();
}
class MyPageState extends State<MyPage> {
ScrollController _scroll;
FocusNode _focus = new FocusNode();
#override void initState() {
super.initState();
_scroll = new ScrollController();
_focus.addListener(() {
_scroll.jumpTo(-1.0);
});
}
#override Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Some Page Title'),
),
body: new DropdownButtonHideUnderline(
child: new SafeArea(
top: false,
bottom: false,
child: new ListView(
controller: _scroll,
padding: const EdgeInsets.all(16.0),
children: <Widget>[
// ... several other input widgets which force the TextField lower down the page ...
new TextField(
decoration: const InputDecoration(labelText: 'The label'),
focusNode: _focus,
),
],
),
),
),
);
}
}