Calling a function after Widget build(BuildContext context) - flutter

I am into flutter to port my android app from java. One thing that is evident in flutter is widgets. Now my biggest obstacle to make my app work as it was on android is starting an async task to request data from the server. I have a custom progress dialog that can be shown or hidden.
class MySelection extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MySelectionState();
}
}
class MySelectionState extends State<MySelection> {
final globalKey = new GlobalKey<ScaffoldState>();
ProgressDialog progressDialog = ProgressDialog.getProgressDialog("Loading books ...");
List<Book> books;
void requestData() async {
EventObject eventObject = await getBooks();
books = eventObject.object;
populateData();
}
#override
Widget build(BuildContext context) {
if (books == null) {
books = List<Book>();
requestData();
}
var appBar = AppBar();
return Scaffold(
appBar: AppBar(
title: Text('Set up your Collection'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
books = List<Book>();
requestData();
},
),
],
),
body: SingleChildScrollView(
child: Stack(
Container(
height: (MediaQuery.of(context).size.height - (appBar.preferredSize.height * 2)),
padding: const EdgeInsets.symmetric(horizontal: 10),
margin: EdgeInsets.only(top: 50.0),
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: books.length,
itemBuilder: bookListView,
),
),
Container(
height: (MediaQuery.of(context).size.height),
padding: const EdgeInsets.symmetric(horizontal: 10),
child: progressDialog,
),
],
),
),
}
}
Now, this code works well when I don't call the progress dialog unlike when I try to do that by calling my progressdialog widget.
if (books == null) {
progressDialog.showProgress();
books = List<Book>();
requestData();
}
It throws the error that
The method 'showProgress' was called on null. Receiver: null Tried
calling: showProgress()
Of course, the reason is that I am calling this before its widget is even created. Now my question is how can I do this because I can't afford to put a button for the user to click. I just want this to work on its own once the user is on this particular screen.

import 'package:flutter/scheduler.dart';
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
// add your code which you want to execute after your build is complete
});
}
Thanks.

Related

How to dynamically add a TextFormField with initialization data in the middle of a list of UI TextFormFields?

I have a list of dynamic forms where I need to add and remove form fields between two fields dynamically. I am able to add/remove form fields from the bottom of the list properly.
However, when I try to add a form field in between two form fields the data for the field does not update correctly.
How can I correctly add a field in between the two fields and populate the data correctly?
import 'package:flutter/material.dart';
class DynamicFormWidget extends StatefulWidget {
const DynamicFormWidget({Key? key}) : super(key: key);
#override
State<DynamicFormWidget> createState() => _DynamicFormWidgetState();
}
class _DynamicFormWidgetState extends State<DynamicFormWidget> {
List<String?> names = [null];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dynamic Forms'),
),
body: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
itemBuilder: (builderContext, index) => Row(
children: [
Flexible(
child: TextFormField(
initialValue: names[index],
onChanged: (name) {
names[index] = name;
debugPrint(names.toString());
},
decoration: InputDecoration(
hintText: 'Enter your name',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8))),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: IconButton(
onPressed: () {
setState(() {
if(index + 1 == names.length){
names.add( null); debugPrint('Added: $names');
} else {
names.insert(index + 1, null); debugPrint('Added [${index+1}]: $names');
}
});
},
color: Colors.green,
iconSize: 32,
icon: const Icon(Icons.add_circle)),
),
Padding(
padding: const EdgeInsets.all(8),
child: IconButton(
onPressed: (index == 0&& names.length == 1)
? null
: () {
setState(() {
names.removeAt(index);
});
debugPrint('Removed [$index]: $names');
},
color: Colors.red,
iconSize: 32,
icon: const Icon(Icons.remove_circle)),
),
],
),
separatorBuilder: (separatorContext, index) => const SizedBox(
height: 16,
),
itemCount: names.length,
),
);
}
}
Basically the problem is that Flutter is confused about who is who in your TextFormField list.
To fix this issue simply add a key to your TextFormField, so that it can be uniquely identified by Flutter:
...
child: TextFormField(
initialValue: names[index],
key: UniqueKey(), // add this line
onChanged: (name) {
...
If you want to learn more about keys and its correct use take a look at this.
The widget AnimatedList solves this problem, it keep track of the widgets as a list would do and uses a build function so it is really easy to sync elements with another list. If you end up having a wide range of forms you can make use of the InheritedWidget to simplify the code.
In this sample i'm making use of the TextEditingController to abstract from the form code part and to initialize with value (the widget inherits from the ChangeNotifier so changing the value will update the text in the form widget), for simplicity it only adds (with the generic text) and removes at an index.
To make every CustomLineForm react the others (as in: disable remove if it only remains one) use a StreamBuilder or a ListModel to notify changes and make each entry evaluate if needs to update instead of rebuilding everything.
class App extends StatelessWidget {
final print_all = ChangeNotifier();
App({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FormList(print_notifier: print_all),
floatingActionButton: IconButton(
onPressed: print_all.notifyListeners,
icon: Icon(Icons.checklist),
),
),
);
}
}
class FormList extends StatefulWidget {
final ChangeNotifier print_notifier;
FormList({required this.print_notifier, super.key});
#override
_FormList createState() => _FormList();
}
class _FormList extends State<FormList> {
final _controllers = <TextEditingController>[];
final _list_key = GlobalKey<AnimatedListState>();
void print_all() {
for (var controller in _controllers) print(controller.text);
}
#override
void initState() {
super.initState();
widget.print_notifier.addListener(print_all);
_controllers.add(TextEditingController(text: 'Inital entrie'));
}
#override
void dispose() {
widget.print_notifier.removeListener(print_all);
for (var controller in _controllers) controller.dispose();
super.dispose();
}
void _insert(int index) {
final int at = index.clamp(0, _controllers.length - 1);
_controllers.insert(at, TextEditingController(text: 'Insert at $at'));
// AnimatedList will take what is placed in [at] so the controller
// needs to exist before adding the widget
_list_key.currentState!.insertItem(at);
}
void _remove(int index) {
final int at = index.clamp(0, _controllers.length - 1);
// The widget is replacing the original, it is used to animate the
// disposal of the widget, ex: size.y -= delta * amount
_list_key.currentState!.removeItem(at, (_, __) => Container());
_controllers[at].dispose();
_controllers.removeAt(at);
}
#override
Widget build(BuildContext context) {
return AnimatedList(
key: _list_key,
initialItemCount: _controllers.length,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
itemBuilder: (ctx, index, _) {
return CustomLineForm(
index: index,
controler: _controllers[index],
on_insert: _insert,
on_remove: _remove,
);
},
);
}
}
class CustomLineForm extends StatelessWidget {
final int index;
final void Function(int) on_insert;
final void Function(int) on_remove;
final TextEditingController controler;
const CustomLineForm({
super.key,
required this.index,
required this.controler,
required this.on_insert,
required this.on_remove,
});
#override
Widget build(BuildContext context) {
return Row(
children: [
Flexible(
child: TextFormField(
controller: controler,
),
),
IconButton(
icon: Icon(Icons.add_circle),
onPressed: () => on_insert(index),
),
IconButton(
icon: Icon(Icons.remove_circle),
onPressed: () => on_remove(index),
)
],
);
}
}

how to achieve a functionality like linear loading bar which will load up as user move between various screens

I am using android studio and flutter. I want to build the screen as shown below in the image:screen Image
let's say I have 4 screens. on the first screen, the bar will load up to 25%. the user will move to next screen by clicking on continue, the linearbar will load up to 50% and so on. the user will get back to previous screens by clicking on the back button in the appbar.
I tried stepper but it doesn't serve my purpose.
You can use the widget LinearProgressIndicator(value: 0.25,) for the first screen and with value: 0.5 for the second screen etc.
If you want to change the bar value within a screen, just use StatefullWidget's setState(), or any state management approaches will do.
import 'package:flutter/material.dart';
class ProgressPage extends StatefulWidget {
const ProgressPage({super.key});
#override
State<ProgressPage> createState() => _ProgressPageState();
}
class _ProgressPageState extends State<ProgressPage> {
final _pageController = PageController();
final _pageCount = 3;
int? _currentPage;
double? _screenWidth;
double? _unit;
double? _progress;
#override
void initState() {
super.initState();
_pageController.addListener(() {
_currentPage = _pageController.page?.round();
setState(() {
_progress = (_currentPage! + 1) * _unit!;
});
});
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_screenWidth = MediaQuery.of(context).size.width;
_unit = _screenWidth! / _pageCount;
_progress ??= _unit;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('HOZEROGOLD')),
body: Column(
children: [
Align(
alignment: Alignment.topLeft,
child: Container(
color: Colors.yellow,
height: 10,
width: _progress,
),
),
Expanded(
child: PageView(
controller: _pageController,
children: _createPage(),
),
),
],
),
);
}
List<Widget> _createPage() {
return List<Widget>.generate(
_pageCount,
(index) => Container(
color: Colors.white,
child: Center(
child: ElevatedButton(
onPressed: () => _moveNextPage(),
child: Text('NEXT $index'),
),
),
),
);
}
void _moveNextPage() {
if (_pageController.page!.round() == _pageCount-1) {
_pageController.jumpToPage(0);
} else {
_pageController.nextPage(
curve: Curves.bounceIn,
duration: const Duration(milliseconds: 100));
}
}
}
HAPPY CODING! I hope it will be of help.

Flutter oddly not reloading widget

I have been working with flutter just a while now but I have never experienced such a weird problem. basically I'm checking if there is a logged on username show them the main page and if not show them the signup page. after the user signs up (and logs in at the same time) I want to take him to my main page. even though I return a new Scaffold the mobile screen doesn't change at all. not with a hot load or anything. but after stopping the program and running it again (because the user is logged in) it automatically goes to my main page (which I want to do without having to stop the program and running it again. any ideas why this is happening couldn't find anything related to this.
import 'package:sociagrowapp/models/user.dart';
import 'package:sociagrowapp/Authenticate/SignIn.dart';
import 'package:sociagrowapp/HomePages/Home.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Wrapper extends StatefulWidget{
#override
createState() => _Wrapper();
}
class _Wrapper extends State<Wrapper> {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
print(user);
// return either the Home or Authenticate widget
if (user == null){
print('Should Changed 3');
return Scaffold(
body: SignIn()
);
}
else {
print('Should Changed');
print('Should Changed2');
return PageData()
}
}
}
Just in case I will add the PagesData Code but I don't think it is related to that.
import 'package:flutter/material.dart';
import 'package:sociagrowapp/services/auth.dart';
int selectedbotnavi = 0;
class DailyTasks extends StatefulWidget
{
#override
createState() => _DailyTasks();
}
class _DailyTasks extends State<DailyTasks>
{
Widget build(BuildContext context)
{
return Center(
child: Text("15")
);
}
}
class Settings extends StatefulWidget
{
#override
createState() => _Settings();
}
class _Settings extends State<Settings>
{
String _httpdataretrieved;
Widget build(BuildContext context)
{
return Column(
children: <Widget>[
Container(width:MediaQuery.of(context).size.width,
child: Text('Your Account Username',style: TextStyle(fontWeight: FontWeight.w400),),
alignment: Alignment.center,
padding: EdgeInsetsDirectional.fromSTEB(0, 20, 0, 0),
),
Container(width:MediaQuery.of(context).size.width,
child: Text(' Important: Your Account must be public for SociaGrow. \n There are limited Features available to private Accounts',style: TextStyle(fontWeight: FontWeight.w900,fontSize:14),
),
alignment: Alignment.center,
padding: EdgeInsetsDirectional.fromSTEB(0, 5, 0, 20),
),
Container(child: TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Username',
),
),
width: MediaQuery.of(context).size.width * 0.8,
alignment: Alignment.center,
padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 15),
),
Container(
child: RaisedButton(
child: Text('Change Username'),
),
)
],
);
}
}
List <Widget> Pages = [new DailyTasks(),new DailyTasks(),new DailyTasks()];
class PageData extends StatefulWidget
{
#override
createState() => _PageData();
}
class _PageData extends State<PageData>
{
void _changeselectbotnaviindex(int index)
{
selectedbotnavi = index;
setState(() {
});
}
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context)
{
return Scaffold(
appBar: AppBar(title: Container(
child: Image.asset('assets/Logo.png',width: 100,height: 200,),
padding: EdgeInsetsDirectional.fromSTEB(0, 10, 0 , 0),
),
actions: <Widget>[
FlatButton(
child: Text('Sign out'),
onPressed: () async {
await this._auth.signOut();
},
),
],
),
body: Pages[selectedbotnavi],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items :[
BottomNavigationBarItem(icon: Icon(Icons.timelapse),title:Text('Daily Tasks')),
BottomNavigationBarItem(icon: Icon(Icons.call_made),title:Text('Growth')),
BottomNavigationBarItem(icon: Icon(Icons.settings),title:Text('Settings')),],
currentIndex: selectedbotnavi,
onTap: _changeselectbotnaviindex,
selectedItemColor: Colors.amber[800],
unselectedItemColor: Colors.black,
showUnselectedLabels: true,
)
);
}
}
That is not the way you navigate to a new page in Flutter.
In Flutter, the way to navigate between pages is with Navigator, which is a widget that manages a set of child widgets with a stack discipline. That is, Navigator has everything ready for you to navigate between pages easily. When you create an app with MaterialApp, Flutter attaches a Navigator to the top of the widget tree for you under the hood. This way, you can access the Navigator in your widget tree via context, by calling Navigator.of(context).
In your case, when the user taps the sign up button in your sign up page, you should do something like:
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => PageData()));
This way, your app will navigate to PageData when the user signs in.
Check out this Flutter.dev article on navigating pages for more details on the topic.
You have to call SetState() so your build method is called again.
You could add a VoidListener to your SignIn(onLogin:(){setState(() {});})

does setState work on objects in dart/flutter?

I have a flutter widget that attempts to solve soduku grids. I have class called SodukuSolver which does all the calculations and provides a List<String> of the current results. I call setState to refresh the list, but it does not update the screen.
Below, I'll try to include as much of the relevant code as I can. Full source is at https://github.com/mankowitz/soduku
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: "Soduku solver", home: Soduku());
}
}
class SodukuState extends State<Soduku> {
SodukuSolver ss;
List<String> _list;
int _changes = 0;
int _remaining = 81;
#override
Widget build(BuildContext context) {
final String _starting =
"750943002024005090300020000140089005093050170500360024000070009070400810400198057";
ss = new SodukuSolver(_starting);
_list = ss.getList();
return Scaffold(
appBar: AppBar(title: Text('Soduku solver'), actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.directions_walk),
onPressed: () {
_iterate();
},
),
]),
body: _buildGrid(),
);
}
Widget _buildGrid() {
return Column(children: <Widget>[
AspectRatio(
aspectRatio: 1.0,
child: Container(
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 9,
),
itemBuilder: _buildGridItems,
itemCount: 81,
),
),
),
]);
}
Widget _buildGridItems(BuildContext context, int index) {
return GestureDetector(
child: GridTile(
child: Container(
child: Center(
child: Text(_list[index]),
),
),
),
);
}
void _iterate() {
setState(() {
_changes = ss.iterateSoduku();
_remaining = ss.empties();
_list = ss.getList();
});
}
}
class Soduku extends StatefulWidget {
#override
SodukuState createState() => SodukuState();
}
So the problem is that _iterate() is being called, and I can use the debugger to see that the internal state of SodukuSolver is being updated and it is even passing _list correctly, but the grid on screen doesn't update, even though _changes and _remaining do update.
You are creating new SodukuSolver with same _starting every time the widget builds and then obtaining _list from it. So you are overriding changes from previous iteration.
Looks like SodukuSolver creation should be performed once. You can override initState in SodukuState and initialise SodukuSolver there or initialise it in the same place where it is declared
Just add your code in the initState() method as following
#override
void initState() {
super.initState();
final String _starting =
"750943002024005090300020000140089005093050170500360024000070009070400810400198057";
ss = new SodukuSolver(_starting);
_list = ss.getList();
}
In your case, your list is not getting updated as setState() method will call your SodukuSolver() and ss.getList(); methods every time. because, setSate() ultimately calls build method to render every time.
So adding it inside your initState method will solve your issue. As it is getting called only once when the screen/route initialises.

How to implement a Slider within an AlertDialog in Flutter?

I am learning app development on Flutter and can't get my Slider to work within the AlertDialog. It won't change it's value.
I did search the problem and came across this post on StackOverFlow:
Flutter - Why slider doesn't update in AlertDialog?
I read it and have kind of understood it. The accepted answer says that:
The problem is, dialogs are not built inside build method. They are on a different widget tree. So when the dialog creator updates, the dialog won't.
However I am not able to understand how exactly does it have to be implemented as not enough background code is provided.
This is what my current implementation looks like:
double _fontSize = 1.0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(qt.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.format_size),
onPressed: () {
getFontSize(context);
},
),
],
),
body: ListView.builder(
padding: EdgeInsets.symmetric(vertical: 15.0),
itemCount: 3,
itemBuilder: (context, index) {
if (index == 0) {
return _getListTile(qt.scripture, qt.reading);
} else if (index == 1) {
return _getListTile('Reflection:', qt.reflection);
} else {
return _getListTile('Prayer:', qt.prayer);
}
})
);
}
void getFontSize(BuildContext context) {
showDialog(context: context,builder: (context){
return AlertDialog(
title: Text("Font Size"),
content: Slider(
value: _fontSize,
min: 0,
max: 100,
divisions: 5,
onChanged: (value){
setState(() {
_fontSize = value;
});
},
),
actions: <Widget>[
RaisedButton(
child: Text("Done"),
onPressed: (){},
)
],
);
});
}
Widget parseLargeText(String text) {...}
Widget _getListTile(String title, String subtitle) {...}
I understand that I will need to make use of async and await and Future. But I am not able to understand how exactly. I've spent more than an hour on this problem and can't any more. Please forgive me if this question is stupid and noobish. But trust me, I tried my best.
Here is a minimal runnable example. Key points:
The dialog is a stateful widget that stores the current value in its State. This is important because dialogs are technically separate "pages" on your app, inserted higher up in the hierarchy
Navigator.pop(...) to close the dialog and return the result
Usage of async/await
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double _fontSize = 20.0;
void _showFontSizePickerDialog() async {
// <-- note the async keyword here
// this will contain the result from Navigator.pop(context, result)
final selectedFontSize = await showDialog<double>(
context: context,
builder: (context) => FontSizePickerDialog(initialFontSize: _fontSize),
);
// execution of this code continues when the dialog was closed (popped)
// note that the result can also be null, so check it
// (back button or pressed outside of the dialog)
if (selectedFontSize != null) {
setState(() {
_fontSize = selectedFontSize;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Font Size: ${_fontSize}'),
RaisedButton(
onPressed: _showFontSizePickerDialog,
child: Text('Select Font Size'),
)
],
),
),
);
}
}
// move the dialog into it's own stateful widget.
// It's completely independent from your page
// this is good practice
class FontSizePickerDialog extends StatefulWidget {
/// initial selection for the slider
final double initialFontSize;
const FontSizePickerDialog({Key key, this.initialFontSize}) : super(key: key);
#override
_FontSizePickerDialogState createState() => _FontSizePickerDialogState();
}
class _FontSizePickerDialogState extends State<FontSizePickerDialog> {
/// current selection of the slider
double _fontSize;
#override
void initState() {
super.initState();
_fontSize = widget.initialFontSize;
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Font Size'),
content: Container(
child: Slider(
value: _fontSize,
min: 10,
max: 100,
divisions: 9,
onChanged: (value) {
setState(() {
_fontSize = value;
});
},
),
),
actions: <Widget>[
FlatButton(
onPressed: () {
// Use the second argument of Navigator.pop(...) to pass
// back a result to the page that opened the dialog
Navigator.pop(context, _fontSize);
},
child: Text('DONE'),
)
],
);
}
}
You just need to warp the AlertDialog() with a StatefulBuilder()