Flutter - Complex functions in initState() slowing down navigation and performance - android

I am working on this app, where you can navigate to a screen with a stateful widget that has a bunch complex functions that run in initState().
Due to these functions, navigating to this screen takes close to two seconds after the Navigation function is triggered i.e It is very slow
My Code looks like this
#override
void initState(){
someComplexHeavyFunctions();
super.initState();
}
Is there any way to make sure the navigation has completed (fast and smmothly) before running the function in initState() and also, maybe showing a loader while the functions are still processing after the screen has been navigated to?
like this:

You can use the compute function to do the calculation in a different isolate which runs on a different thread.

You can call the initializing function and while awaiting for it, display the dialog.
This is a complete example:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Center(
child: FloatingActionButton(
child: Text("Go"),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => OtherPage()));
},
),
),
);
}
}
class OtherPage extends StatefulWidget {
#override
_OtherPageState createState() => _OtherPageState();
}
class _OtherPageState extends State<OtherPage> {
bool initialized = false;
#override
void initState() {
super.initState();
initialize();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await showDialog<String>(
context: context,
builder: (BuildContext context) => new AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CircularProgressIndicator(),
SizedBox(height: 40.0,),
Text("Performing task"),
],
),
),
);
});
}
Future<void> initialize() async {
initialized = await Future.delayed(Duration(seconds: 5), () => true);
Navigator.of(context).pop();
setState(() {});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(),
body: new Center(
child: initialized ? Text("Initialized") : Container(),
),
);
}
}

Related

Flutter: "Looking up a deactivated widget's ancestor is unsafe" in a Stateless Widget

I am new to Flutter, and this problem really bothers me, I has searched through the Internet, however, none of the results satisfies me:
I try to use Progress Dialog from the package:
import 'package:progress_dialog/progress_dialog.dart';
And the class MyApp in my main.dart file is like this:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Declare and decorate my Progress Dialog.
ProgressDialog pr = ProgressDialog(
context,
type: ProgressDialogType.Normal,
isDismissible: false,
);
pr.style(
message: 'Fetching Something...',
borderRadius: 50.0,
elevation: 5.0,
);
// TODO: implement build method
return MaterialApp(
home: Scaffold(
body: RaisedButton.icon(
onPressed: () async {
pr.show();
await fetchData();
pr.hide();
},
icon: Icon(Icons.clear),
label: Text('Fetch Data'),
),
),
);
}
}
And my example fetchData() function is like this (of course the package and installation steps for Firestore's functions are validated):
Future<void> fetchData() async {
// Just an example of really fetching something.
await Firestore.instance
.collection('users')
.document('0')
.delete();
}
What I want is that, every time I click the button, a loading spinner is showed and hides immediately after fetchData() function finishes. This yields a correct flow in the first click, however, if I click the button for the second time, the spinner is not show (the fetchData() function still executes properly). And a warning (not an error) is displayed in the Terminal:
I/flutter (17942): Exception while showing the dialog
I/flutter (17942): Looking up a deactivated widget's ancestor is unsafe.
I/flutter (17942): At this point the state of the widget's element tree is no longer stable.
I/flutter (17942): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
The documents on dependOnInheritedWidgetOfExactType() are quite limited and difficult to understand. So I still have no idea of how to solve this problem correctly.
Any help is really appreciated. Thank you.
You can copy paste run full code below
You can use await pr.show(); and await pr.hide();
code snippet
onPressed: () async {
await pr.show();
await fetchData();
await pr.hide();
},
working demo
full code
import 'package:flutter/material.dart';
import 'package:progress_dialog/progress_dialog.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
Future<void> fetchData() async {
await Future.delayed(Duration(seconds: 3), () {});
setState(() {});
}
#override
Widget build(BuildContext context) {
ProgressDialog pr = ProgressDialog(
context,
type: ProgressDialogType.Normal,
isDismissible: false,
);
pr.style(
message: 'Fetching Something...',
borderRadius: 50.0,
elevation: 5.0,
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton.icon(
onPressed: () async {
await pr.show();
await fetchData();
await pr.hide();
},
icon: Icon(Icons.clear),
label: Text('Fetch Data'),
),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

Is there a better way to add an AppBar only once in flutter app and use it in all routes without duplicating the same AppBar code in those routes?

I'm trying to find a way to add an AppBar only once without having to duplicate the AppBar code on different routes. I have tried different approaches but I still cant get the results that I want.
This is the main.dart file.
import 'package:flutter/material.dart';
import 'package:com.example.simple_app/pages/page_one.dart';
import 'package:com.example.simple_app/pages/page_one.dart';
void main() => runApp(SimpleApp());
final routes = {
'/': (BuildContext context) => new PageOne(),
'/pageone': (BuildContext context) => new PageOne(),
'/pagetwo': (BuildContext context) => new PageTwo(),
};
class SimpleApp extends StatefulWidget {
#override
_SimpleAppState createState() => _SimpleAppState();
}
class _SimpleApp extends State<SimpleApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Simple App',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blueGrey,
),
initialRoute: '/',
routes: routes,
);
}
}
Below is PageOne.dart file where I've hardcoded AppBar code.
import 'package:flutter/material.dart';
class PageOne extends StatefulWidget {
PageOne({Key key}) : super(key: key);
#override
_PageOneState createState() => _PageOneState();
}
class _PageOneState extends State<PageOne> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Simple AppBar'),
),
);
}
}
Below is PageTwo.dart file where I've also put AppBar by writing the whole code, I'm duplicating AppBar code which is not cool.
import 'package:flutter/material.dart';
class PageOne extends StatefulWidget {
PageOne({Key key}) : super(key: key);
#override
_PageTwoState createState() => _PageOneState();
}
class _PageTwoState extends State<PageOne> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Simple AppBar'),
),
);
}
}
I'm new to flutter but I hope you understand what I'm saying.
What I want is to be able to use AppBar in different routes without duplicating the code. For example when we go to web development, A website built in php you can simply include views. I want something similar here or better, Thank you.
You can create a separate App bar Widget for achieving this Like :
Create an appbar.dart file as :
import 'package:flutter/material.dart';
Widget appbar(BuildContext context, String title, dynamic otherData) {
return AppBar(
title: Text(title),
//Other data you want to show
);
}
And import the appear. dart file wherever you want to display an App bar.
Screen1:
import 'appbar.dart';
import 'package:flutter/material.dart';
class Page1 extends StatefulWidget {
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appbar(context, 'Chat App', {'icons' : Icons.menu}),
);
}
}
Screen2:
import 'appbar.dart';
import 'package:flutter/material.dart';
class Page12 extends StatefulWidget {
#override
_Page12State createState() => _Page12State();
}
class _Page12State extends State<Page12> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appbar(context, 'Chat App', {'icons' : Icons.menu}),
);
}
}
There are a lot more ways to achieve this. This is the simple one I tried.
Use builder property in MaterialApp to build common container for every page
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: Theme.of(context).copyWith(highlightColor: Colors.amber),
//TODO: Use `builder` to add top most container for all page
builder: (context, child) {
return Scaffold(
appBar: AppBar(
title: Text("Simple AppBar"),
),
body: child, //this child is dynamically replaced with corresponding page when we navigate
);
},
home: FirstPage(),
debugShowCheckedModeBanner: false,
);
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("This is First Page"),
RaisedButton(
child: Text("Goto Second Page"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(),
),
);
},
),
],
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("This is First Page"),
RaisedButton(
child: Text("Go Back"),
onPressed: () {
Navigator.pop(context);
},
),
],
);
}
}
First, create a file for example appBar.dart
then place your personal appBar there like this:
import 'package:flutter/material.dart';
final appBar = AppBar(
backgroundColor: Colors.orange,
elevation: 5.0,
title: Center(
child: SizedBox(
height: 40.0,
child:Image.asset("assets/images/myLogo.png"),
),
),
);
now import and use the appBar anywhere you want.
import 'package:myApp/pages/appBar.dart';
return Scaffold(
appBar: appBar,
body:......

Flutter: Navigation issue

I'm not able to navigate screen showing error 'Navigator operation requested with a context that does not include a Navigator, I have tried many solutions where navigator is used in Builder with stateless widgets but here navigation is done automatically after a few seconds in override method in intiSate. my aim is to navigate the screen after a few seconds.
class Splash extends StatelessWidget {
#override
Widget build(BuildContext context) {
return testWidget;
}
}
Widget testWidget = new MediaQuery(
data: new MediaQueryData(),
child: new MaterialApp( title: 'xxxxxxxxxxxxx',
home: SplashScreen(),
debugShowCheckedModeBanner: false,
routes: <String, WidgetBuilder>{
'/login': (BuildContext context) => new Login(),
},
)
);
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => new _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
Future initState () {
super.initState();
new Future.delayed(
const Duration(seconds: 2), () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => Login()),
));
}
#override
Widget build(BuildContext context) {
return(Scaffold(
body: Container(
height: double.infinity,
width: double.infinity,
child: Image.asset('assets/images/crop.jpg',fit:BoxFit.fill),
),
));
//build
}
}
Showing Error
Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
Code Corrected:
MaterialApp Should always be the Root Widget of all Widgets.
That Way Navigator is always Available.
void main() => runApp(Splash());
class Splash extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: testWidget,
debugShowCheckedModeBanner: false,
);
}
}
Widget testWidget =
new MediaQuery(data: new MediaQueryData(), child: new SplashScreen());
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => new _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
Future.delayed(
const Duration(seconds: 2),
() => Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => Login()),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Spalsh'),),
body: Container(
height: double.infinity,
width: double.infinity,
// child: Image.asset('assets/images/crop.jpg', fit: BoxFit.fill),
),
);
//build
}
}
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login Page'),
),
body: Container(),
);
}
}

how to fix flutter exception : Navigator operation requested with a context that does not include a Navigator

I am trying to create a drawer navigation using flutter framework,
but i am getting the following exception every time I run it
Another exception was thrown: Navigator operation requested with a
context that does not include a Navigator.
so what is the solution, any help ?
I used Navigator class as the following
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return new AppStates();
}
}
class AppStates extends State<MyApp> {
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text("Application App Bar"),
),
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Next Page"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => NextPage()));
},
)
],
),
),
),
);
}
}
and the code of the NextPage class is
class NextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Next Page App Bar"),
),
),
);
}
}
It looks like you don't have a Navigator setup for current context. Instead of using StatefulWidget you should try MaterialApp as your root App. MaterialApp manages a Navigator for you. Here is an example of how to setup an App in your main.dart
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: MyApp(),
));
}
This is because the context that you're using is from the app level before a Navigator has actually been created. This is a common problem when creating "simple" single file apps in Flutter.
There are a number of possible solutions. One is to extract your Drawer into it's own class (extend Stateless/StatefulWidget accordingly), then in it's build override, the parent Scaffold will have already been created containing a Navigator for you to use.
class MyDrawer extend StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Next Page"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => NextPage()));
},
)
],
),
);
}
The other, if you want to keep this Drawer in the same file, is to use a Builder instead, which has the same effect:
drawer: Builder(builder: (context) =>
Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Next Page"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => NextPage()));
},
)
],
),
),
),
you need to create a new Widget as home in MaterialApp like this:-
(This worked for me)
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen());
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Title"),
),
body: Center(child: Text("Click Me")),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
backgroundColor: Colors.orange,
onPressed: () {
print("Clicked");
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddTaskScreen()),
);
},
),
);
}
}

Is there any way intercept 'Back' keydown in Flutter app on Android?

I need to show an alert dialog before user navigates away from current route by pressing Back button on Android devices. I tried to intercept back button behavior by implementing WidgetsBindingObserver in widget state. There is an closed issue on GitHub regarding same topic. However my code is not working as the method didPopRoute() was never called. Here is my code below:
import 'dart:async';
import 'package:flutter/material.dart';
class NewEntry extends StatefulWidget {
NewEntry({Key key, this.title}) :super(key: key);
final String title;
#override
State<StatefulWidget> createState() => new _NewEntryState();
}
class _NewEntryState extends State<NewEntry> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
Future<bool> didPopRoute() {
return showDialog(
context: context,
child: new AlertDialog(
title: new Text('Are you sure?'),
content: new Text('Unsaved data will be lost.'),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: new Text('No'),
),
new FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: new Text('Yes'),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.edit),
onPressed: () {},
),
);
}
}
I found the solution is to use WillPopScope widget. Here is the final code below:
import 'dart:async';
import 'package:flutter/material.dart';
class NewEntry extends StatefulWidget {
NewEntry({Key key, this.title}) :super(key: key);
final String title;
#override
State<StatefulWidget> createState() => new _NewEntryState();
}
class _NewEntryState extends State<NewEntry> {
Future<bool> _onWillPop() {
return showDialog(
context: context,
child: new AlertDialog(
title: new Text('Are you sure?'),
content: new Text('Unsaved data will be lost.'),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: new Text('No'),
),
new FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: new Text('Yes'),
),
],
),
) ?? false;
}
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: _onWillPop,
child: new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.edit),
onPressed: () {},
),
),
);
}
}
The back_button_interceptor package can simplify this for you and is especially useful in more complex scenarios.
https://pub.dev/packages/back_button_interceptor#-readme-tab-
Example usage:
#override
void initState() {
super.initState();
BackButtonInterceptor.add(myInterceptor);
}
#override
void dispose() {
BackButtonInterceptor.remove(myInterceptor);
super.dispose();
}
bool myInterceptor(bool stopDefaultButtonEvent) {
print("BACK BUTTON!"); // Do some stuff.
return true;
}
If you are using the GetX package and you implemented the GetMaterialApp method to initialize your app, the didPopRoute and didPushRoute methods in WidgetsBindingObserver never get called. Use the routingCallback instead, below is an example, for more info check out GetX documentation:
GetMaterialApp(
routingCallback: (routing) {
routing.isBack ? didPopRoute() : didPushRoute(routing.current);
}
)

Categories

Resources