I want to show a list of data with list view and JSON. This code in a file with stateless widget. And this page opened after login. When I try in stateful widget, the code RUN normally. In stateless widget, when I debug it, the code didn't call function getData(). But directly went to
Widget build(BuildContext context) {
return new Scaffold( ...
Here is complete code :
class frmClass extends StatelessWidget{
List<dynamic> dta;
get setState => null;
Future<String> getData() async {
var uri ='https://xxxx.com/class';
var map = new Map<String, dynamic>();
map["username"] = "abc";
map["password"] = "1234";
http.Response response = await http.post(
uri,
body: jsonEncode(map),
);
Map<String,dynamic> mp = jsonDecode(utf8.decode(response.bodyBytes));
this.setState(() {
dta = mp["data"];
debugPrint(dta.toString());
});
}
#override
void initState(){
this.getData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
backgroundColor:Colors.transparent,
elevation: 0.0,
iconTheme: new IconThemeData(color: Color(0xFF18D191))),
body: new ListView.builder(
itemCount: dta == null ? 0 : dta.length,
itemBuilder: (BuildContext context, int index){
return new Card(
child: new Text(dta[index]["className"]),
);
}
),
);
}
}
How can I fix it?
You can use a FutureBuilder to call getData() into build() method of StatelessWidget:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
But, this will fire getData() every time your statelessWidget is rebuild.
Another way is to use reactive programing architecture (like Bloc, rxdart, etc..).
Depends on what do you want, fire getData() every time or just once (or when your conditions are true/false).
This is happening because stateless widgets don't have initState that's why the below code will never get called. Stateless widgets are more sort of rendering flat UI with no lifecycle methods if you want to use Stateless widget then pass data in the class constructor and use it where ever you want
#override
void initState(){
this.getData();
}
if you want to call it from a stateless widget? Well, that’s possible too. Use a stateful widget as a your root widget that you can provide a callback function too to execute your startup logic. See example below.
Create a StatefulWrapper that takes in a function to call and a child to display.
/// Wrapper for stateful functionality to provide onInit calls in stateles widget
class StatefulWrapper extends StatefulWidget {
final Function onInit;
final Widget child;
const StatefulWrapper({#required this.onInit, #required this.child});
#override
_StatefulWrapperState createState() => _StatefulWrapperState();
}
class _StatefulWrapperState extends State<StatefulWrapper> {
#override
void initState() {
if(widget.onInit != null) {
widget.onInit();
}
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
Wrap your stateles widget’s UI in a StatefulWrapper and pass on the onInit logic you would like to run
class StartupCaller extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StatefulWrapper(
onInit: () {
_getThingsOnStartup().then((value) {
print('Async done');
});
},
child: Container(),
);
}
Future _getThingsOnStartup() async {
await Future.delayed(Duration(seconds: 2));
}
}
and that’s it. The function will only be called once when the stateful widget is initialized.
This solution from fluttercampus solved my problem , where I wanted to call some method before build inside stateless widget
https://www.fluttercampus.com/guide/230/setstate-or-markneedsbuild-called-during-build/
#override
Widget build(BuildContext context) {
Future.delayed(Duration.zero,(){
CheckIfAlreadyLoggedIn();
});
return Container();
}
Related
I want to calculate every pages stay duration of my flutter app. The stay duration means from page becomes visible to page hide.
How do i do this?
Any idea might help me.
You can use this wrapper widget,
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: PageView.builder(
itemCount: 4,
itemBuilder: (context, index) => TrackWidgetLifeTime(
child: Text("$index"),
lifeTime: (spent) {
log("I've spend ${spent.toString()}");
},
),
),
),
);
}
}
class TrackWidgetLifeTime extends StatefulWidget {
const TrackWidgetLifeTime({super.key, this.lifeTime, required this.child});
final Function(Duration spent)? lifeTime;
final Widget child;
#override
State<TrackWidgetLifeTime> createState() => _TrackWidgetLifeTimeState();
}
class _TrackWidgetLifeTimeState extends State<TrackWidgetLifeTime> {
var stopwatch = Stopwatch();
#override
void initState() {
super.initState();
stopwatch.start();
}
#override
void dispose() {
if (widget.lifeTime != null) widget.lifeTime!(stopwatch.elapsed);
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
An idea would be to capture the time at initState for stateful widgets, or under build method for stateless widgets, and compare it with the time on leave, as follows:
late DateTime started;
DateTime? ended;
#override
void initState() {
started = DateTime.now();
super.initState();
}
void onLeave() {
ended = DateTime.now();
if (ended != null) {
var lapse = Duration(microseconds: ended!.microsecondsSinceEpoch-started.microsecondsSinceEpoch);
print("Viewed page ${lapse.inSeconds} seconds");
}
}
To learn how to detect when the user moves away from the current page take a look at this answer:
Detect if the user leaves the current page in Flutter?
I'm new at Flutter and still struggling with overall concept of widgets structure especially stateful ones. As far as I understand I should create all widgets stateless unless they can be changed. Then they should be stateful and hold their state.
So I have a simple screen with a list of items that is dynamic and action button:
class WalletWidget extends StatelessWidget {
final GlobalKey<_WalletItemsState> _key = GlobalKey();
void _addWalletItem(BuildContext context) async {
var asset = await Navigator.pushNamed(context, '/add_wallet_item');
if (asset == null) {
return;
}
_key.currentState!._refresh();
}
#override
Widget build(BuildContext context) => Scaffold(
body: _WalletItems(),
floatingActionButton: FloatingActionButton(
onPressed: () => _addWalletItem(context),
child: const Icon(Icons.add)),
);
}
class _WalletItems extends StatefulWidget {
const _WalletItems();
#override
State<StatefulWidget> createState() => _WalletItemsState();
}
class _WalletItemsState extends State<_WalletItems> {
List<Asset> _walletAssets = [];
void _refresh() {
setState(() {});
}
#override
Widget build(BuildContext context) => ListView.builder(
itemCount: _walletAssets.length,
itemBuilder: (context, index) =>
Text(Asset.persistenceMgr.getAll().toList()[index].title),
);
}
in my understanding whole screen should be a stateless as it merely builds a UI structure. Hence class WalletWidget extends StatelessWidget. The list widget is stateful. Adding of new elements is done at separate screen when the action button is pressed. So when I'm back from adding screen the list is updated and I need to refresh the state of the list.
Only way I've found is using a GlobalKey, which points to the _WalletItemsState. But when it comes to line _key.currentState!._refresh(); the currentState is always null. So I assume the _key isn't properly associated with list's state. How do I do that?
_key is just intialized and not used in any widget. So, the currentState is always null
Try passing the _key to Scaffold of WalletWidget.
class WalletWidget extends StatelessWidget {
final GlobalKey<_WalletItemsState> _key = GlobalKey();
void _addWalletItem(BuildContext context) async {
var asset = await Navigator.pushNamed(context, '/add_wallet_item');
if (asset == null) {
return;
}
_key.currentState!.build(context);
}
#override
Widget build(BuildContext context) => Scaffold(
key:_key,
body: _WalletItems(),
floatingActionButton: FloatingActionButton(
onPressed: () => _addWalletItem(context),
child: const Icon(Icons.add)),
);
}
I am making a list of stateless widget as shown below and passing the id as the parameter to the widgets.
Code for cartPage:-
class Cart extends StatefulWidget {
#override
_CartState createState() => _CartState();
}
class _CartState extends State<Cart> {
bool loading=true;
List<CartTile> cartTiles=[];
#override
void initState() {
super.initState();
if(currentUser!=null)
getData();
}
getData()async
{
QuerySnapshot snapshot=await cartReference.doc(currentUser.id).collection('cartItems').limit(5).get();
snapshot.docs.forEach((doc) {
cartTiles.add(CartTile(id: doc.data()['id'],index: cartTiles.length,));
});
setState(() {
loading=false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: loading?Center(child:CircularProgressIndicator():SingleChildScrollView(
child: Column(
children: cartTiles,
),
),
);
}
}
Code for CartTile:-
class CartTile extends StatelessWidget {
final String id;
CartTile({this.id,});
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: productReference.doc(id).snapshots(),
builder: (context,snapshot)
{
//here am using the snapshot to build the cartTile.
},
);
}
}
So, my question is whenever I will call setState in my homepage then will the stateless widget be rebuilt and increase my document reads. Because i read somewhere that when we pass the same arguments or parameters to a stateless widget then due to its cache mechanism it doesn't re build. If it will increase my reads then is there any other way to solve this problem?
How to separate setState in Abc class?
I have lots of this kind of methods in one class. Now, my class is too long. Is there any way to separate this class to subclass or another way?
class _AbcState extends State<Abc> {
String name = 'name';
double number = 15.5;
int value = 10;
//REMOVE FROM ABC class
void changeName() {
setState(() {
name = 'new Name';
});
}
void changeNumber() {
setState(() {
number = 10.0;
});
}
void changeValue() {
setState(() {
value = 5;
});
}
//
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
There are 2 ways to do this:
First:
You can separate the individual input fields into their stateful widgets if necessary and let them handle their individual states internally, only giving you the changed data in respective onChange callbacks.
This is the simplest and quickest solution.
A better approach is to:
Second:
Use a library/dependency/package for the state management in your application:
This is an example of the Provider flutter package with ChangeNotifier:
You can check the official docs here for detailed explanations and examples:
Provider
ChangeNotifier
Simple example:
Create a class extending the ChangeNotifier:
Whenever the some data changes in the ChangeNotifer you can call the notifyListeners() method.
class UserProvider extends ChangeNotifier {
String username;
String password;
void updateUsername(String username) {
this.username = username;
notifyListeners();
}
void updatePassword(String password) {
this.password = password;
notifyListeners();
}
}
Wrap your widget tree in a ChangeNotifierProvider widget:
This widget will let you access the instance of the ChangeNotifier class anywhere in down the widget tree like InheritedWidget.
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<UserProvider>(
lazy: false,
create: (context) => UserProvider(),
child: Home(),
);
}
}
Access the instance of ChangeNotifier from anywhere down the widget tree with and call any methods or data variables:
/// Get an instance of the UserProvider in the ancestors of the current widget tree like this.
UserProvider userProvider = Provider.of<UserProvider>(context);
/// Call any method inside the UserProvider class like this
userProvider.updateUsername("John Doe");
/// access any data variables inside the UserProvider class like this.
User userInfo = userProvider.username;
Changing UI based on data/state changes in ChangeNotifier:
You can use the Consumer and Selector widgets in the provider package, which provide an efficient ways to redraw the UI based on certain parameters of the ChangeNotifier class, when the notifyListeners() method is called from the ChangeNotifier class.
Consumer:
This widget will simply rebuild the entire widget tree wrapped inside it, whenever ANY data changes inside the ChangeNotifier with notifyListeners().
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<UserProvider>(
builder: (context, userProvider, _) {
return Scaffold(
appBar: AppBar(
title: const Text(userProvider.username),
),
body: Center(
child: Text(
"Your password is: ${userProvider.password}!",
),
),
);
},
);
}
}
Selector:
This widget will allow for more finer control over your widget rebuild. It will rebuild only part of the widget based on specific data variable changes inside the ChangeNotifier.
The below example will only rebuild the Text widget if the password data changes inside our UserProvider ChangeNotifier class.
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<UserProvider>(
builder: (context, userProvider, _) {
return Scaffold(
appBar: AppBar(
title: const Text(userProvider.username),
),
body: Center(
child: Selector<UserProvider, String>(
selector: (context, userProvider) => userProvider.password,
builder: (context, state, child) {
return Text(
"Your password is: ${userProvider.password}!",
);
},
),
),
);
},
);
}
}
We want to show an AlertDialog after some asynchronous processing such as network processes.
When calling 'showAlertDialog ()' from an external class, I want to call it without context. Is there a good way?
class SplashPage extends StatelessWidget implements SplashView {
BuildContext _context;
#override
Widget build(BuildContext context) {
this._context = context;
...
}
I've considered the above method, but I'm worried about side issues.
Help
My current code
class SplashPage extends StatelessWidget implements SplashView {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyStoreColors.eats_white1_ffffff,
body: Center(
child: new SvgPicture.asset('assets/ic_splash.svg'),
),
);
}
#override
void showAlertDialog() {
showDialog<void>(
context: /*How to get context?*/,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Not in stock'),
content: const Text('This item is no longer available'),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
#override
void moveToHomeContainer() {
}
#override
void moveToLoginContainer() {
}
}
To show an AlertDialog you need the context, but in StatelessWidget you do not have access to it directly as in StatefulWidget.
Few options are [1]:
passing it as GlobalKey [2]
passing build context as parameter to any other function inside StatelessWidget
use a service to inject the dialog without context [3]
Cheers.
You should trigger rebuild when the async event complete, either convert your widget to StatefulWidget and call setState() or use a state management solution like Bloc.
For example using StatefulWidget your code will look like this:
class SplashPage extends StatefulWidget {
#override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> implements SplashView {
bool _asynOpDone = false;
/// Call this when the async operation is done.
void _onAsynOpDone() => setState(() => _asyncOpDone = true);
#override
Widget build(BuildContext context) {
if (_asyncOpDone) showAlertDialog(context);
return Scaffold(
...,
///
);
}
#override
void showAlertDialog(BuildContext context) {
showDialog<void>(
context: context,
builder: ...,
);
}
}
You can apply Builder pattern concept to simplify this.
There is a little example here.
button_builder.dart