Initializing state of late variables in flutter - flutter

class _HomeViewState extends State<HomeView> {
late UserInfo currentUser;
#override
void didChangeDependencies() async {
super.didChangeDependencies();
await dbService.getUserInfo(uid).then((value) {
setState(() {
currentUser = value!;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
Container(
child: currentUser != null
? welcomeText(currentUser)
: const Center(
child: CircularProgressIndicator(),
),
),
...
Why is it not possible to do something like this?
The following LateError was thrown building HomeView(dirty, state: _HomeViewState#c79dc):
LateInitializationError: Field 'currentUser' has not been initialized.
Getting this error. How do I initialize Future/late variables and use them in Widgets without using FutureBuilder? FutureBuilder takes way too many lines of code.
initState() seems to be sync so it can't be used

“Late” means “this variable will be initialized late”, It will be initialized late but never null, so currentUser never null, If the Late is not initialized, then we get the red screen of death. This is something that should never happen.
use UserInfo? currentUser instead of late UserInfo
class _HomeViewState extends State<HomeView> {
UserInfo? currentUser;
#override
void didChangeDependencies() async {
super.didChangeDependencies();
await dbService.getUserInfo(uid).then((value) {
setState(() {
currentUser = value!;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
Container(
child: currentUser != null
? welcomeText(currentUser!)
: const Center(
child: CircularProgressIndicator(),
),
),
...

Related

How do I call method initState() from a stateless widget extending class?

I have a splash screen in my homepage activity which should then redirect to my second activity:
class _MyHomePageState extends State<MyHomePage> {
#override
void initState(){
super.initState();
Timer(const Duration(seconds: 3),
()=>Navigator.pushReplacement(context,
MaterialPageRoute(builder:
(context) =>
SecondScreen()
)
)
);
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child:FlutterLogo(size:MediaQuery.of(context).size.height)
);
}
}
class SecondScreen extends StatelessWidget { //checking if internet connection exists here
late StreamSubscription subscription;
var isDeviceConnected = false;
bool isAlertSet = false;
#override
void initState(){
getConnectivity();
super.initState(); //initState() is undefined
}
getConnectivity() =>
subscription = Connectivity().onConnectivityChanged.listen(
(ConnectivityResult result) async {
isDeviceConnected = await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
showDialogBox();
setState(() => isAlertSet = true); //setState() is undefined
}
},
);
#override
void dispose() {
subscription.cancel();
super.dispose(); //dispose() is undefined
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment:MainAxisAlignment.center,
children:[
Image(
image: const AssetImage('images/logo.png'),
height: AppBar().preferredSize.height,),
const SizedBox(
width: 15,
),
Text(
widget.title
),
]
)
)
);
}
showDialogBox() => showCupertinoDialog<String>(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: const Text('No internet connection'),
content: const Text('Please make sure you have an active internet connection to continue'),
actions: <Widget>[
TextButton(
onPressed: () async {
Navigator.pop(context, 'Cancel');
setState(() => isAlertSet = false);
isDeviceConnected =
await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
showDialogBox();
setState(() => isAlertSet = true);
}
},
child: const Text('OK'),
),
],
),
);
}
The flow is such that, in the homepage activity a splash screen will open and then it will redirect to the second activity which will check if the user has an active internet connection.
I tried changing the SecondScreen to statefulWidget, but I still keep getting the same error.
Stateless: A stateless widget is like a constant. It is immutable. If you want to change what is displayed by a stateless widget, you'll have to create a new one.
Stateful: Stateful widgets are the opposite. They are alive and can interact with the user. Stateful widgets have access to a method named setState, which basically says to the framework "Hello, I want to display something else. Can you redraw me please ?".
A stateless widget can only be drawn once when the Widget is loaded/built and cannot be redrawn based on any events or user actions.
This kind of widget has no state, so they can’t change according to an internal state, they only react to higher widget changes.
more information read this documentation StatefulWidget and StatelessWidget
convert in stateful widget
class SecondScreen extends StatefulWidget {
const SecondScreen({Key? key}) : super(key: key);
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
there is no initState in a stateless widget but you can call a function after rebuild of a stateless widget using this:
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
// do something
print("Build Completed");
});
return Container(
color: Colors.blue,
child: WhatEverWidget()
);
}
}

Flutter: How to extract arguments which comes from another widget in a StatefulWidget?

i'm trying to extract the ModalRoute as a Global value in a StatefulWidget but it's not working, i can extract it locally under Widget build(BuildContext context) and it will work but the Global methods and widgets that i'm working on wont work, please help :'(
Here is my code,
it starts from here:
home.dart
GestureDetector(
onTap: ()async{
await Navigator.of(context).pushNamed(MainTankHomePage.routeName, arguments: widget.tankID);
//widget.tankID is a String and i extracted it in MainTankHomePage.dart with ModalRoute as a String it works perfectly so no need to change anything here
},
MainTankHomePage.dart
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
import 'package:smart_tank1/main_tank_detail_ui/home/bottom_nav_bar.dart';
import 'package:smart_tank1/main_tank_detail_ui/hydration_pool/hydration_pool_page.dart';
import 'package:smart_tank1/main_tank_detail_ui/hydration_progress/hydration_progress_page.dart';
import 'package:smart_tank1/main_tank_detail_ui/summary/summary_page.dart';
class MainTankHomePage extends StatefulWidget {
static const routeName = 'main-screen';
#override
_MainTankHomePageState createState() => _MainTankHomePageState();
}
class _MainTankHomePageState extends State<MainTankHomePage> {
//------------------------------------------
//Here is my global methods that i worked on
//------------------------------------------
late final tanksID = ModalRoute.of(context)!.settings.arguments as String; // added late to get the (context) work without error line but it didn't work
final _pages = <Widget>[
//----------------------------------------
//Here is the problem that i'm facing!
//Done all of the parameters work in each widget with a required tankID
//So what i need here is just passing the extracted ModalRoute here which is the tanksID to each widget but it's not working
//-----------------------------------------
MainTankHydrationPoolPage(tankID: tanksID,),
MainHydrationProgressPage(tankID: tanksID,),
SummaryPage(tanksID: tanksID),
//-----------------------------
//Here is the error i get
//String tanksID
//package:smart_tank1/main_tank_detail_ui/home/main_tank_home_page.dart
//The instance member 'tanksID' can't be accessed in an initializer.
//Try replacing the reference to the instance member with a different //expression
//-------------------------------
];
int _currentPage = 0;
void _changePage(int index) {
if (index == _currentPage) return;
setState(() {
_currentPage = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
PageTransitionSwitcher(
transitionBuilder: (
child,
primaryAnimation,
secondaryAnimation,
) {
return FadeThroughTransition(
fillColor: Theme.of(context).backgroundColor,
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
child: _pages[_currentPage],
),
BottomNavBar(
currentPage: _currentPage,
onChanged: _changePage,
),
],
),
);
}
}
It is possible to get null value from ModalRoute, I will suggest using nullable data.
late final String? tanksID = ModalRoute.of(context)?.settings.arguments as String?;
And pass default value while it gets null
MainTankHydrationPoolPage(tankID: tanksID??"default id",),
Or ignore build
if(tanksID!=null) MainTankHydrationPoolPage(tankID: tanksID!,),
Make sure checking null before using !.
late List<Widget> _pages;
#override
void initState() {
_pages = [
.....,
];
super.initState();
}
Check more about null-safety and check this answer.

What is the right way using the build context outside the build function

Lets say I'm enter my named route page and get the arguments in the build function.
Now my widget is state full widget and i want to make api call with the arguments in order to set the state of my widget.
I'm using future Builder to load the api when the page is loading, so i have to create Future and equal him to the api func right?
but i cant do it inside the build it will call it unlimited times, so i send it as props to an other widget but really i should create widget just in order to send my context values?
class GameScreen extends StatefulWidget {
GameScreen({Key key}) : super(key: key);
#override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
Fixture fixture;
Future setFIxture(externalId) async {
final response =
await FixturesService().getLiveFixtureByExternalId(externalId);
setState(() {
fixture = response;
});
}
#override
Widget build(BuildContext context) {
final GameScreenArguments args = ModalRoute.of(context).settings.arguments;
Future initScreen;
initScreen = setFIxture(args.externald);
return RoutePage(
child: Loader(
future: initScreen,
succeed: Container(
height: 223,
width: double.infinity,
child: Column(
children: [
Column(
children: [
Text(""),
Row(
children: [
Text(""),
Text(""),
],
),
Column(
children: [
// TeamImage(),
Column(
children: [
Text(""),
Text(""),
],
),
// TeamImage(),
],
)
],
),
Column(
children: [
Text(""),
],
)
],
),
),
),
);
}
}
my loader widget:
class Loader extends StatefulWidget {
final Future future;
final Widget succeed;
Loader({Key key, this.future, this.succeed}) : super(key: key);
#override
_LoaderState createState() => _LoaderState();
}
class _LoaderState extends State<Loader> {
Future _getTaskAsync;
final spinkit = SpinKitFadingCircle(
color: Colors.black,
size: 40,
);
Future fetchData() async {
try {
await widget.future;
return true;
} catch (e) {
return e;
}
}
#override
void initState() {
_getTaskAsync = fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getTaskAsync,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return widget.succeed;
} else if (snapshot.hasError) {
return Text("error");
} else {
return spinkit;
}
},
);
}
}
Yes you have to create another widget. But maybe use a dependency injection solution to make it simpler to inject objects into the widget tree (Riverpod is good i heard). Store the GameScreen args inside a shared state above in the widget tree.

FlutterError (setState() called after dispose(): (lifecycle state: defunct, not mounted)

the error is thrown in two areas (and the app freezes (when the app is minimized, when phones back button is clicked, or when another app runs on top of the flutter app. Flutter version: 1.20.2 (previous versions did not have this issue): The two functions are:
#override
void initState() {
super.initState();
getItems();
}
getItems() async {
initClearVisibility();
initFilters();
setState(() {
loadingItems = true;
Visibility(visible: true, child: CircularProgressIndicator());
});
QuerySnapshot querySnapshot = await query.get();
items = querySnapshot.docs;
lastDocument = querySnapshot.docs[querySnapshot.docs.length - 1];
setState(() {
loadingItems = false;
Visibility(visible: false, child: CircularProgressIndicator());
});
}
initClearVisibility() {
if (Str.filterSelectCategory != Str.CATEGORY) {
clearCategoryVisible = true;
allCategoriesVisible = false;
categoryValue = Str.filterSelectCategory;
setState(() {});
}
}
initFilters() async {
filterDefaultItems();
}
filterDefaultItems() async {
query = _firestore
.collection(Str.ITEMS)
.where(Str.IS_ITEM_SOLD, isEqualTo: false)
.where(Str.ADDRESS, isEqualTo: userAddress1)
//.orderBy(Str.DATE_POSTED)
.limit(perPage);
}
Second area is on the following code where I am also getting: :
class FABBottomAppBarItem {
FABBottomAppBarItem({this.iconData, this.itemColor}); //, this.text});
IconData iconData;
var itemColor;
//String text;
}
class FABBottomAppBar extends StatefulWidget {
FABBottomAppBar({
this.items,
this.centerItemText,
this.height: 65.0,
this.iconSize: 24.0,
this.backgroundColor,
this.color,
this.selectedColor,
this.notchedShape,
this.onTabSelected,
}) {
assert(this.items.length == 2 || this.items.length == 4);
}
final List<FABBottomAppBarItem> items;
final String centerItemText;
final double height;
final double iconSize;
final Color backgroundColor;
final Color color;
final Color selectedColor;
final NotchedShape notchedShape;
final ValueChanged<int> onTabSelected;
#override
State<StatefulWidget> createState() => FABBottomAppBarState();
}
class FABBottomAppBarState extends State<FABBottomAppBar> {
//int _selectedIndex = 0;
int unreadCount = 0;
_updateIndex(int index) {
widget.onTabSelected(index);
setState(() {
//_selectedIndex = index;
});
}
#override
void initState() {
super.initState();
countDocuments();
}
#override
Widget build(BuildContext context) {
List<Widget> items = List.generate(widget.items.length, (int index) {
return _buildTabItem(
item: widget.items[index],
index: index,
onPressed: _updateIndex,
);
});
items.insert(items.length >> 1, _buildMiddleTabItem());
return BottomAppBar(
shape: widget.notchedShape,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: items,
),
color: widget.backgroundColor,
);
}
Widget _buildMiddleTabItem() {
return Expanded(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.075, //widget.height,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).size.height * 0.04,
),
Text(
widget.centerItemText ?? '',
style: TextStyle(
color: BwerereTheme.bwerereRed,
fontSize: 14.0,
fontWeight: FontWeight.w900),
),
],
),
),
);
}
Widget _buildTabItem({
FABBottomAppBarItem item,
int index,
ValueChanged<int> onPressed,
})
{
return Expanded(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.065,
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () => onPressed(index),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Stack(
children: <Widget>[
Icon(item.iconData,
color: item.itemColor,
size: IconTheme.of(context).size * 1.2),
index == 2 ? badge() : Container()
],
)
],
),
),
),
),
);
}
Widget badge() => unreadCount < 1
? Container()
: Container(
padding: EdgeInsets.all(4.0),
decoration: BoxDecoration(
color: BwerereTheme.bwerereRed, shape: BoxShape.circle),
child: Center(
child: RobotoFont(
text: "$unreadCount",
textSize: 12.0,
textColor: Colors.white,
fontWeight: FontWeight.w400),
));
void countDocuments() async {
final uid = await FetchUserData().getCurrentUserID();
QuerySnapshot _myDoc = await FirebaseFirestore.instance
.collection("userUnreadMessages")
.doc(uid)
.collection(Str.MESSAGE_COLLECTION)
.get();
List<DocumentSnapshot> _myDocCount = _myDoc.docs;
setState(() {
unreadCount = _myDocCount.length;
print('NOTIY LENGTH::: $unreadCount');
});
}
}
THE ERROR FROM FRAMEWORK.DART for FABBottomAppBarState.
The same error thrown on the getItems on HomePage()
Exception has occurred.
FlutterError (setState() called after dispose(): FABBottomAppBarState#250ac(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().)
Further investigation then shows that the app takes about 400MB of memory (Ram) for the phone which I find rather too high.
Help on figuring out the issue will really help. Thanks in advance.
Additional information:
Error occurs when running on android 7.0, flutter 1.20.2. See similar/related issue on https://github.com/flutter/flutter/issues/35900. Note that I upgraded to Flutter 1.20.2 adnd Downgrading to 1.7.5 will require a lot of changes I made after upgrading especially on Firestore (NOTE: https://firebase.flutter.dev/docs/migration which was recently updated).
After an await, your widget may not be mounted anymore. Doing setState gives you an exception at that time. This is actually a good thing, the code that follows should not be executing anyway, since you are somewhere else.
You have three options about the "setState() called after dispose()" exception:
Safely ignore it. The exception is saving your async function from continuing. You will see an exception in your logs that you can just ignore.
Place a if (!mounted) return; between each await and setState(). It may be a good habit to put it after each await. This also stops the async function and hides the exception, if you are allergic to it.
Replace your setState() calls with setStateIfMounted() and define it as:
void setStateIfMounted(f) {
if (mounted) setState(f);
}
However, if (mounted) setState() does not stop the async function, so this 3rd option is the worst between the three as discussed here.
I also explain these approaches in this video.
You can use:
if (this.mounted) { // check whether the state object is in tree
setState(() {
// make changes here
});
}
The mounted checks whether Whether this State object is currently in a tree.
mounted class
For beginners and easy to understand check bool isMount = true; when dispose() would be called bool isMount = false; and setState() won't be called.
class TotalBooks extends StatefulWidget {
TotalBooks({Key? key}) : super(key: key);
// code omitted..
bool isMount = true;
#override
_TotalBooksState createState() => _TotalBooksState();
}
class _TotalBooksState extends State<TotalBooks> {
#override
void initState() {
// code omitted..
if (widget.isMount) {
setState(() {
// code omitted..
});
}
});
super.initState();
}
#override
void dispose() {
// TODO: implement dispose
widget.isMount = false;
// code omitted.. super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox();
}
}
#override
void setState(VoidCallback fn) {
if (!mounted) return;
super.setState(fn);
}
I found there is no that perfect and easy way! I have written a subclass extends the State, you can use VMState instead of State, then just call safeSetState instead of setState.
import 'package:flutter/widgets.dart';
class VMState<T extends StatefulWidget> extends State<T> {
bool didDispose = false;
#override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
#override
void dispose() {
didDispose = true;
super.dispose();
}
void safeSetState(VoidCallback callback) {
if (!didDispose) {
setState(callback);
}
}
}
Someone says to use mounted but that brings another exception.
With this code, my error has been solved!
if (mounted) {
setState(() {
// make your changes here
});
}

Trigger snackbar message on page load in Flutter

I am new to Flutter and trying to trigger a snack bar on page load if a message was returned from the page I navigated from. I have managed to get the message to display on a button click, but get an error stating that my context does not have a Scaffold if I try to do it elsewhere.
I am also struggling to find an example of how to show a sack bar without user interaction, so if anyone has a reference, that would surely go a long way in helping as well.
Here is a simplified version of my view:
class LandingView extends StatefulWidget {
final LandingViewModel viewModel;
LandingView(this.viewModel);
#override
State<StatefulWidget> createState() {
return new _ViewState();
}
}
class _ViewState extends State<LandingView> {
#override
void initState() {
super.initState();
}
void _showSnackbar(context, message) {
final snackBar = SnackBar(
content: Text(message),
);
Scaffold.of(context).showSnackBar(snackBar);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: new GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: _buildLayout(context),
),
),
);
}
Widget _buildLayout(BuildContext context) {
Map<String, dynamic> args = getArgs(context); //get value from previous page
if (args != null &&
args["Toast Message"] != null) //check if a value was returned from the previous page. This has been tested and a valid string is being returned
_showSnackbar(
context, args["Toast Message"]); //if so call snack bar function
// this throws an error saying "Scaffold.of() called with a context that does not contain a Scaffold"
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: boxConstraints.maxHeight),
child: RaisedButton(
child: Text(
"Show Snack Bar",
),
onPressed:
() {
if (args != null &&
args["Toast Message"] !=
null) //check if a value was returned from the previous page. This has been tested and a valid string is being returned
_showSnackbar(context,
args["Toast Message"]); //if so call snack bar function
//this works perfectly
}),
),
);
});
}
}
Any advice would be greatly appreciated
You're getting that because your LandingView widget is not in a Scaffold. You can fix this by putting the LandingView widget inside a StatelessWidget with a Scaffold and changing any references to LandingView to LandingViewPage:
class LandingViewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: LandingView()
);
}
}
We can do this with addPostFrameCallback method
#override
void initState(){
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => scaffold.showSnackBar(SnackBar(content: Text("snackbar")));
}
In a stateful widget put:
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Error")));
});
}