How to control null when bulding widget - flutter

Can you tell me how can I include the condition for Image.file on the following page? I would like to build it only when controller.image is not null.
I got an error:
The following NoSuchMethodError was thrown building Container(padding: EdgeInsets.all(32.0)):
The method '[]' was called on null.
Receiver: null
Tried calling:
when I first redirect to this page (and controller.image is null):
class HomePage extends GetView<HomeController> {
final myController1 = TextEditingController();
final myController2 = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Grobonet'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
controller: myController1,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Nazwisko'),
),
TextField(
controller: myController2,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Miejscowosc'),
),
IconButton(
icon: Icon(
Icons.add,
size: 30,
),
onPressed: () =>
Get.toNamed(
AppRoutes.PICK_IMAGE
),
color: Colors.pink,
),
IconButton(
icon: Icon(
Icons.save_outlined,
size: 30,
),
/*onPressed: () =>
Get.toNamed(
AppRoutes.PICK_IMAGE
),*/
color: Colors.pink,
),
Container(
padding: EdgeInsets.all(32),
child: GetBuilder<HomeController>(
builder: (_) {
return Image.file(controller.image);
},
),
),
]
),
),
);
}
}
Controller:
class HomeController extends GetxController {
final image = Get.arguments['image'];
final file_loaded = Get.arguments['file_loaded'];
}

You can use collection-if. Just add if (controller.image != null) like so:
if (controller?.image != null)
Container(
padding: EdgeInsets.all(32),
child: GetBuilder<HomeController>(
builder: (_) {
return Image.file(controller.image);
},
),
),

Use Get your arguments on the controller's onInit() method:
class HomeController extends GetxController {
final File image;
final File file_loaded;
onInit(){
image = Get.arguments['image'];
file_loaded = Get.arguments['file_loaded'];
}
}
In this way, you don't need to perform additional null checks except Get.arguments is actually null.
Update
And you also need to update your view like this:
Container(
padding: EdgeInsets.all(32),
child: GetBuilder<HomeController>(
builder: (_) {
return Image.file(_.image); // not return Image.file(controller.image);
// _ here is the instance of HomeController given by the GetBuilder
},
),
),
Update 2
As you mentioned in the comments, you want to send data to a previously opened page (HomePage) from a second page (OCRDetailsPage). Then you don't need to pass arguments. You can get the HomeController instance in your OCRDetailsPage with Get.find() and set the variables and update the state like:
class OCRDetailsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final OCRDetailsController controller = Get.find();
return Scaffold(
appBar: AppBar(title: Text('OCR Details Page')),
body: Center(
child: FlatButton(
color: Colors.blue,
child: Icon(Icons.save_outlined),
onPressed: () {
final HomeController homeController = Get.find();
homeController.ocr_text = controller.text;
homeController.update();
Get.toNamed(
AppRoutes.HOME,
);
}),
),
);
}
}

Related

flutter transfer data (color) to create a new widget

I'm creating a calendar app. The problem that I'm now facing is that I want to create a new user of the calendar. The user has the properties (which are now important) image, name and color.
I created a new File For the property color, in which the color can be changed. But I don't know how I can transfer the new color in the other file, so that I can use it to create the user.
I think it is possible to use the Material page route, but perhaps there is a more elegant way to handle this.
Does someone have an idea to handle this in a easy way?
UserSetScreen:
import 'package:calendar_vertical/screens/users_show_screen.dart';
import 'package:calendar_vertical/widgets/color_choose.dart';
import 'package:calendar_vertical/widgets/image_input.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class UserSetScreen extends StatefulWidget {
static const routeName = '/userSetScreen';
#override
State<UserSetScreen> createState() => _UserSetScreenState();
}
class _UserSetScreenState extends State<UserSetScreen> {
final _titleController = TextEditingController();
static const values = <String>[
'Administrator',
'normaler Nutzer',
'eingeschränkter Nutzer'
];
String selectedValue = values.first;
void _saveValues(User user) {
final neuerNutzer = User(
id: DateTime.now().toString(),
name: _titleController.text,
color: Colors.amber,
setAppointments: false,
administrator: false,
);
}
#override
Widget build(BuildContext context) {
final colorData = Provider.of<ColorChoose>(context);
return Scaffold(
appBar: AppBar(
title: Text('Person hinzufügen'),
actions: [
IconButton(
onPressed: () {
Navigator.of(context).pushNamed(UsersShowScreen.routeName);
},
icon: Icon(Icons.people),
),
],
),
body: Column(
children: [
Center(
child: ImageInput(),
),
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(10),
child: Column(
children: [
TextField(
decoration: InputDecoration(labelText: 'Name'),
controller: _titleController,
),
ColorChoose(),
//CheckboxListTile(
// value: value,
// onChanged: (value) => setState(() => this.value = value!),
// title: Text('Administrator'),
// controlAffinity: ListTileControlAffinity.leading,
//)
],
),
),
))
],
),
);
}
ColorChoose:
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
class ColorChoose extends StatefulWidget {
#override
State<ColorChoose> createState() => _ColorChooseState();
}
class _ColorChooseState extends State<ColorChoose> {
Color currentColor = Colors.white;
#override
Widget build(BuildContext context) {
return Row(
children: [
Text('Farbe: '),
Container(
decoration: BoxDecoration(
color: currentColor,
borderRadius: BorderRadius.all(
Radius.circular(15),
),
),
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
margin: EdgeInsets.only(left: 10.0),
),
Spacer(),
ElevatedButton(
onPressed: () => _showColorPicker(context),
child: Text(
'Farbe ändern',
),
),
],
);
}
void _showColorPicker(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Farbe wählen'),
titlePadding: const EdgeInsets.all(0.0),
contentPadding: const EdgeInsets.all(0.0),
content: SingleChildScrollView(
child: Wrap(
children: [
Container(
width: 300,
height: 300,
child: BlockPicker(
pickerColor: currentColor,
onColorChanged: (color) => setState(
() => this.currentColor = color,
),
),
)
],
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Close'),
)
],
),
);
}
}
Thank you very much.
Best regards
Patrick
I guess the best variant is to use GetX or another state manager.
Another way - to choose color right from the user screen, showing a dialog.
Finally you can pass valuenotifier to your color ColorChoose widget.

How to fix "Too many positional arguments: 1 expected, but 3 found." issue in flutter

I'm new to flutter.
I need to get product information through a form using flutter provider.
I can get one object(like String name value only). But when I add multiple parameters, it shows the following error.
Too many positional arguments: 1 expected, but 3 found.
This is the code I wrote.
Model class
class Item {
String itemName;
String description;
double itemPrice;
Item(this.itemName, this.description, this.itemPrice);
}
ChangeNotifier class
class ItemAddNotifier extends ChangeNotifier {
List<Item> itemList = [];
addItem(String itemName, String description, double itemPrice) {
Item item = Item(itemName, description, itemPrice);
itemList.add(item);
notifyListeners();
}
}
Add items
class AddItems extends StatelessWidget {
final TextEditingController _itemNameTextEditing = TextEditingController();
final TextEditingController _itemDescriptionTextEditing =
TextEditingController();
final TextEditingController _itemPriceTextEditing = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Kavishka'),
),
body: Container(
padding: EdgeInsets.all(30.0),
child: Column(
children: [
TextField(
controller: _itemNameTextEditing,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
hintText: 'Item Name',
),
),
SizedBox(
height: 20.0,
),
TextField(
controller: _itemDescriptionTextEditing,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
hintText: 'Item Description',
),
),
SizedBox(
height: 20.0,
),
TextField(
controller: _itemPriceTextEditing,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
hintText: 'Item Price',
),
),
SizedBox(
height: 20.0,
),
RaisedButton(
child: Text('ADD ITEM'),
onPressed: () async {
if (_itemNameTextEditing.text.isEmpty) {
return;
}
await Provider.of<ItemAddNotifier>(context, listen: false)
.addItem(
_itemNameTextEditing.text,
_itemDescriptionTextEditing.text,
_itemPriceTextEditing.text);
Navigator.pop(context);
},
),
],
),
),
);
}
}
Home Screen
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Kavishka'),
actions: [
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) {
return AddItems();
},
),
);
},
icon: Icon(Icons.add))
],
),
body: Container(
padding: EdgeInsets.all(30.0),
child: Column(
children: [
Consumer<ItemAddNotifier>(builder: (context, itemAddNotifier, _) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: itemAddNotifier.itemList.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.all(15.0),
child: Column(
children: [
Text(
itemAddNotifier.itemList[index].itemName,
style:
TextStyle(fontSize: 20.0, color: Colors.black),
),
],
),
);
});
})
],
),
),
);
}
}
Main
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) {
return ItemAddNotifier();
},
child: MaterialApp(
home: Container(
color: Colors.white,
child: HomeScreen(),
),
),
);
}
}
It shows the error in Item item = Item(itemName, description, itemPrice); line.
If someone can help me to fix this issue.
Thank you.

Passing value to previous widget

I have simple form , inside it have CircularAvatar when this is pressed show ModalBottomSheet to choose between take picture from gallery or camera. To make my widget more compact , i separated it to some file.
FormDosenScreen (It's main screen)
DosenImagePicker (It's only CircularAvatar)
ModalBottomSheetPickImage (It's to show ModalBottomSheet)
The problem is , i don't know how to passing value from ModalBottomSheetPickImage to FormDosenScreen. Because value from ModalBottomSheetPickImage i will use to insert operation.
I only success passing from third Widget to second Widget , but when i passing again from second Widget to first widget the value is null, and i think the problem is passing from Second widget to first widget.
How can i passing from third Widget to first Widget ?
First Widget
class FormDosenScreen extends StatefulWidget {
static const routeNamed = '/formdosen-screen';
#override
_FormDosenScreenState createState() => _FormDosenScreenState();
}
class _FormDosenScreenState extends State<FormDosenScreen> {
String selectedFile;
#override
Widget build(BuildContext context) {
final detectKeyboardOpen = MediaQuery.of(context).viewInsets.bottom;
print('trigger');
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Tambah Dosen'),
actions: <Widget>[
PopupMenuButton(
itemBuilder: (_) => [
PopupMenuItem(
child: Text('Tambah Pelajaran'),
value: 'add_pelajaran',
),
],
onSelected: (String value) {
switch (value) {
case 'add_pelajaran':
Navigator.of(context).pushNamed(FormPelajaranScreen.routeNamed);
break;
default:
}
},
)
],
),
body: Stack(
fit: StackFit.expand,
children: <Widget>[
SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SizedBox(height: 20),
DosenImagePicker(onPickedImage: (file) => selectedFile = file),
SizedBox(height: 20),
Card(
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormFieldCustom(
onSaved: (value) {},
labelText: 'Nama Dosen',
),
SizedBox(height: 20),
TextFormFieldCustom(
onSaved: (value) {},
prefixIcon: Icon(Icons.email),
labelText: 'Email Dosen',
keyboardType: TextInputType.emailAddress,
),
SizedBox(height: 20),
TextFormFieldCustom(
onSaved: (value) {},
keyboardType: TextInputType.number,
inputFormatter: [
// InputNumberFormat(),
WhitelistingTextInputFormatter.digitsOnly
],
prefixIcon: Icon(Icons.local_phone),
labelText: 'Telepon Dosen',
),
],
),
),
),
SizedBox(height: kToolbarHeight),
],
),
),
Positioned(
child: Visibility(
visible: detectKeyboardOpen > 0 ? false : true,
child: RaisedButton(
onPressed: () {
print(selectedFile);
},
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
color: colorPallete.primaryColor,
child: Text(
'SIMPAN',
style: TextStyle(fontWeight: FontWeight.bold, fontFamily: AppConfig.headerFont),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
textTheme: ButtonTextTheme.primary,
),
),
bottom: kToolbarHeight / 2,
left: sizes.width(context) / 15,
right: sizes.width(context) / 15,
)
],
),
);
}
}
Second Widget
class DosenImagePicker extends StatefulWidget {
final Function(String file) onPickedImage;
DosenImagePicker({#required this.onPickedImage});
#override
DosenImagePickerState createState() => DosenImagePickerState();
}
class DosenImagePickerState extends State<DosenImagePicker> {
String selectedImage;
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: InkWell(
onTap: () async {
await showModalBottomSheet(
context: context,
builder: (context) => ModalBottomSheetPickImage(
onPickedImage: (file) {
setState(() {
selectedImage = file;
widget.onPickedImage(selectedImage);
print('Hellooo dosen image picker $selectedImage');
});
},
),
);
},
child: CircleAvatar(
foregroundColor: colorPallete.black,
backgroundImage: selectedImage == null ? null : MemoryImage(base64.decode(selectedImage)),
radius: sizes.width(context) / 6,
backgroundColor: colorPallete.accentColor,
child: selectedImage == null ? Text('Pilih Gambar') : SizedBox(),
),
),
);
}
}
Third Widget
class ModalBottomSheetPickImage extends StatelessWidget {
final Function(String file) onPickedImage;
ModalBottomSheetPickImage({#required this.onPickedImage});
#override
Widget build(BuildContext context) {
return SizedBox(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
children: <Widget>[
InkWell(
onTap: () async {
final String resultBase64 =
await commonFunction.pickImage(quality: 80, returnFile: ReturnFile.BASE64);
onPickedImage(resultBase64);
},
child: CircleAvatar(
foregroundColor: colorPallete.white,
backgroundColor: colorPallete.green,
child: Icon(Icons.camera_alt),
),
),
InkWell(
onTap: () async {
final String resultBase64 =
await commonFunction.pickImage(returnFile: ReturnFile.BASE64, isCamera: false);
onPickedImage(resultBase64);
},
child: CircleAvatar(
foregroundColor: colorPallete.white,
backgroundColor: colorPallete.blue,
child: Icon(Icons.photo_library),
),
),
],
),
),
);
}
}
The cleanest and easiest way to do this is through Provider. It is one of the state management solutions you can use to pass values around the app as well as rebuild only the widgets that changed. (Ex: When the value of the Text widget changes). Here is how you can use Provider in your scenario:
This is how your model should look like:
class ImageModel extends ChangeNotifier {
String _base64Image;
get base64Image => _base64Image;
set base64Image(String base64Image) {
_base64Image = base64Image;
notifyListeners();
}
}
Don't forget to add getters and setters so that you can use notifyListeners() if you have any ui that depends on it.
Here is how you can access the values of ImageModel in your UI:
final model=Provider.of<ImageModel>(context,listen:false);
String image=model.base64Image; //get data
model.base64Image=resultBase64; //set your image data after you used ImagePicker
Here is how you can display your data in a Text Widget (Ideally, you should use Selector instead of Consumer so that the widget only rebuilds if the value its listening to changes):
#override
Widget build(BuildContext context) {
//other widgets
Selector<ImageModel, String>(
selector: (_, model) => model.base64Image,
builder: (_, image, __) {
return Text(image);
},
);
}
)
}
You could achieve this easily. If you are using Blocs.

How to get value from textfield and display in textfromfield (another screen)

I'm new to flutter, I trying to pass a value from textfield and when i click a button submit, display it in textformfield in another screen, my problem, I don't know the right way to get value
Some Code :
String txt = "";
TextEditingController controllerTxt = new TextEditingController();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: Text('Create'),
actions: <Widget>[
FlatButton(
child: Text('Submit'),
textColor: Colors.white,
onPressed: () {
setState(() {
//txt = (controllerTxt.text);
Navigator.pushNamed(context, '/ResultPage');
});
},
),
],
),
body: new Container(
child: new Column(
children: <Widget>[
new TextField(
controller: controllerTxt,
maxLines: 5,
decoration: new InputDecoration(
),
),
],
),
),
);
}
}
class _ResultPageState extends State<ResultPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: Text('Result'),
),
body: new Container(
padding: EdgeInsets.all(10.0),
child: new Column(
children: <Widget>[
new TextFormField(
decoration: InputDecoration(
labelText: 'Name :',
),
),
new Text("${controllerTxt.text}"),
],
),
),
);
}
}
I have done the same thing by passing data through the constructor
Navigator.push(context,
MaterialPageRoute(builder: (context) => ResultPage(controllerTxt.text)));
class ResultPage extends StatefulWidget {
final String result;
ResultPage(this.result);

How to work with progress indicator in flutter?

I'm newbie in flutter and wanted to know what is better way to add CircularProgressIndicator in my layout. For example, my login view. This view have username, password and login Button. I did want create a overlay layout (with Opacity) that, when loading, show progress indicator like I use in NativeScript, but I'm little confused with how to do and too if it is the better way. On NativeScript, for example, I add IndicatorActivity in main layout and set busy to true or false, so it overlay all view components when is loading.
Edit:
I was able to reach this result:
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _loading = false;
void _onLoading() {
setState(() {
_loading = true;
new Future.delayed(new Duration(seconds: 3), _login);
});
}
Future _login() async{
setState((){
_loading = false;
});
}
#override
Widget build(BuildContext context) {
var body = new Column(
children: <Widget>[
new Container(
height: 40.0,
padding: const EdgeInsets.all(10.0),
margin: const EdgeInsets.fromLTRB(15.0, 150.0, 15.0, 0.0),
decoration: new BoxDecoration(
color: Colors.white,
),
child: new TextField(
decoration: new InputDecoration.collapsed(hintText: "username"),
),
),
new Container(
height: 40.0,
padding: const EdgeInsets.all(10.0),
margin: const EdgeInsets.all(15.0),
decoration: new BoxDecoration(
color: Colors.white,
),
child: new TextField(
decoration: new InputDecoration.collapsed(hintText: "password"),
),
),
],
);
var bodyProgress = new Container(
child: new Stack(
children: <Widget>[
body,
new Container(
alignment: AlignmentDirectional.center,
decoration: new BoxDecoration(
color: Colors.white70,
),
child: new Container(
decoration: new BoxDecoration(
color: Colors.blue[200],
borderRadius: new BorderRadius.circular(10.0)
),
width: 300.0,
height: 200.0,
alignment: AlignmentDirectional.center,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Center(
child: new SizedBox(
height: 50.0,
width: 50.0,
child: new CircularProgressIndicator(
value: null,
strokeWidth: 7.0,
),
),
),
new Container(
margin: const EdgeInsets.only(top: 25.0),
child: new Center(
child: new Text(
"loading.. wait...",
style: new TextStyle(
color: Colors.white
),
),
),
),
],
),
),
),
],
),
);
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Container(
decoration: new BoxDecoration(
color: Colors.blue[200]
),
child: _loading ? bodyProgress : body
),
floatingActionButton: new FloatingActionButton(
onPressed: _onLoading,
tooltip: 'Loading',
child: new Icon(Icons.check),
),
);
}
}
I'm still adapting to the idea of ​​states. This code is within the expected when working with flutter?
In flutter, there are a few ways to deal with Asynchronous actions.
A lazy way to do it can be using a modal. Which will block the user input, thus preventing any unwanted actions.
This would require very little change to your code. Just modifying your _onLoading to something like this :
void _onLoading() {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Dialog(
child: new Row(
mainAxisSize: MainAxisSize.min,
children: [
new CircularProgressIndicator(),
new Text("Loading"),
],
),
);
},
);
new Future.delayed(new Duration(seconds: 3), () {
Navigator.pop(context); //pop dialog
_login();
});
}
The most ideal way to do it is using FutureBuilder and a stateful widget. Which is what you started.
The trick is that, instead of having a boolean loading = false in your state, you can directly use a Future<MyUser> user
And then pass it as argument to FutureBuilder, which will give you some info such as "hasData" or the instance of MyUser when completed.
This would lead to something like this :
#immutable
class MyUser {
final String name;
MyUser(this.name);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<MyUser> user;
void _logIn() {
setState(() {
user = new Future.delayed(const Duration(seconds: 3), () {
return new MyUser("Toto");
});
});
}
Widget _buildForm(AsyncSnapshot<MyUser> snapshot) {
var floatBtn = new RaisedButton(
onPressed:
snapshot.connectionState == ConnectionState.none ? _logIn : null,
child: new Icon(Icons.save),
);
var action =
snapshot.connectionState != ConnectionState.none && !snapshot.hasData
? new Stack(
alignment: FractionalOffset.center,
children: <Widget>[
floatBtn,
new CircularProgressIndicator(
backgroundColor: Colors.red,
),
],
)
: floatBtn;
return new ListView(
padding: const EdgeInsets.all(15.0),
children: <Widget>[
new ListTile(
title: new TextField(),
),
new ListTile(
title: new TextField(obscureText: true),
),
new Center(child: action)
],
);
}
#override
Widget build(BuildContext context) {
return new FutureBuilder(
future: user,
builder: (context, AsyncSnapshot<MyUser> snapshot) {
if (snapshot.hasData) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Hello ${snapshot.data.name}"),
),
);
} else {
return new Scaffold(
appBar: new AppBar(
title: new Text("Connection"),
),
body: _buildForm(snapshot),
);
}
},
);
}
}
For me, one neat way to do this is to show a SnackBar at the bottom while the Signing-In process is taken place, this is a an example of what I mean:
Here is how to setup the SnackBar.
Define a global key for your Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Add it to your Scaffold key attribute
return new Scaffold(
key: _scaffoldKey,
.......
My SignIn button onPressed callback:
onPressed: () {
_scaffoldKey.currentState.showSnackBar(
new SnackBar(duration: new Duration(seconds: 4), content:
new Row(
children: <Widget>[
new CircularProgressIndicator(),
new Text(" Signing-In...")
],
),
));
_handleSignIn()
.whenComplete(() =>
Navigator.of(context).pushNamed("/Home")
);
}
It really depends on how you want to build your layout, and I am not sure what you have in mind.
Edit
You probably want it this way, I have used a Stack to achieve this result and just show or hide my indicator based on onPressed
class TestSignInView extends StatefulWidget {
#override
_TestSignInViewState createState() => new _TestSignInViewState();
}
class _TestSignInViewState extends State<TestSignInView> {
bool _load = false;
#override
Widget build(BuildContext context) {
Widget loadingIndicator =_load? new Container(
color: Colors.grey[300],
width: 70.0,
height: 70.0,
child: new Padding(padding: const EdgeInsets.all(5.0),child: new Center(child: new CircularProgressIndicator())),
):new Container();
return new Scaffold(
backgroundColor: Colors.white,
body: new Stack(children: <Widget>[new Padding(
padding: const EdgeInsets.symmetric(vertical: 50.0, horizontal: 20.0),
child: new ListView(
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center
,children: <Widget>[
new TextField(),
new TextField(),
new FlatButton(color:Colors.blue,child: new Text('Sign In'),
onPressed: () {
setState((){
_load=true;
});
//Navigator.of(context).push(new MaterialPageRoute(builder: (_)=>new HomeTest()));
}
),
],),],
),),
new Align(child: loadingIndicator,alignment: FractionalOffset.center,),
],));
}
}
Create a bool isLoading and set it to false. With the help of ternary operator, When user clicks on login button set state of isLoading to true. You will get circular loading indicator in place of login button
isLoading ? new PrimaryButton(
key: new Key('login'),
text: 'Login',
height: 44.0,
onPressed: setState((){isLoading = true;}))
: Center(
child: CircularProgressIndicator(),
),
You can see Screenshots how it looks while before login is clicked
After login is clicked
In mean time you can run login process and login user. If user credentials are wrong then again you will setState of isLoading to false, such that loading indicator will become invisible and login button visible to user.
By the way, primaryButton used in code is my custom button. You can do same with OnPressed in button.
Step 1: Create Dialog
showAlertDialog(BuildContext context){
AlertDialog alert=AlertDialog(
content: new Row(
children: [
CircularProgressIndicator(),
Container(margin: EdgeInsets.only(left: 5),child:Text("Loading" )),
],),
);
showDialog(barrierDismissible: false,
context:context,
builder:(BuildContext context){
return alert;
},
);
}
Step 2: Call it
showAlertDialog(context);
await firebaseAuth.signInWithEmailAndPassword(email: email, password: password);
Navigator.pop(context);
Example Dialog and login form
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
class DynamicLayout extends StatefulWidget{
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return new MyWidget();
}
}
showAlertDialog(BuildContext context){
AlertDialog alert=AlertDialog(
content: new Row(
children: [
CircularProgressIndicator(),
Container(margin: EdgeInsets.only(left: 5),child:Text("Loading" )),
],),
);
showDialog(barrierDismissible: false,
context:context,
builder:(BuildContext context){
return alert;
},
);
}
class MyWidget extends State<DynamicLayout>{
Color color = Colors.indigoAccent;
String title='app';
GlobalKey<FormState> globalKey=GlobalKey<FormState>();
String email,password;
login() async{
var currentState= globalKey.currentState;
if(currentState.validate()){
currentState.save();
FirebaseAuth firebaseAuth=FirebaseAuth.instance;
try {
showAlertDialog(context);
AuthResult authResult=await firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
FirebaseUser user=authResult.user;
Navigator.pop(context);
}catch(e){
print(e);
}
}else{
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar:AppBar(
title: Text("$title"),
) ,
body: Container(child: Form(
key: globalKey,
child: Container(
padding: EdgeInsets.all(10),
child: Column(children: <Widget>[
TextFormField(decoration: InputDecoration(icon: Icon(Icons.email),labelText: 'Email'),
// ignore: missing_return
validator:(val){
if(val.isEmpty)
return 'Please Enter Your Email';
},
onSaved:(val){
email=val;
},
),
TextFormField(decoration: InputDecoration(icon: Icon(Icons.lock),labelText: 'Password'),
obscureText: true,
// ignore: missing_return
validator:(val){
if(val.isEmpty)
return 'Please Enter Your Password';
},
onSaved:(val){
password=val;
},
),
RaisedButton(color: Colors.lightBlue,textColor: Colors.white,child: Text('Login'),
onPressed:login),
],)
,),)
),
);
}
}
Example from Ui
1. Without plugin
class IndiSampleState extends State<ProgHudPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Demo'),
),
body: Center(
child: RaisedButton(
color: Colors.blueAccent,
child: Text('Login'),
onPressed: () async {
showDialog(
context: context,
builder: (BuildContext context) {
return Center(child: CircularProgressIndicator(),);
});
await loginAction();
Navigator.pop(context);
},
),
));
}
Future<bool> loginAction() async {
//replace the below line of code with your login request
await new Future.delayed(const Duration(seconds: 2));
return true;
}
}
2. With plugin
check this plugin progress_hud
add the dependency in the pubspec.yaml file
dev_dependencies:
progress_hud:
import the package
import 'package:progress_hud/progress_hud.dart';
Sample code is given below to show and hide the indicator
class ProgHudPage extends StatefulWidget {
#override
_ProgHudPageState createState() => _ProgHudPageState();
}
class _ProgHudPageState extends State<ProgHudPage> {
ProgressHUD _progressHUD;
#override
void initState() {
_progressHUD = new ProgressHUD(
backgroundColor: Colors.black12,
color: Colors.white,
containerColor: Colors.blue,
borderRadius: 5.0,
loading: false,
text: 'Loading...',
);
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('ProgressHUD Demo'),
),
body: new Stack(
children: <Widget>[
_progressHUD,
new Positioned(
child: RaisedButton(
color: Colors.blueAccent,
child: Text('Login'),
onPressed: () async{
_progressHUD.state.show();
await loginAction();
_progressHUD.state.dismiss();
},
),
bottom: 30.0,
right: 10.0)
],
));
}
Future<bool> loginAction()async{
//replace the below line of code with your login request
await new Future.delayed(const Duration(seconds: 2));
return true;
}
}
I took the following approach, which uses a simple modal progress indicator widget that wraps whatever you want to make modal during an async call.
The example in the package also addresses how to handle form validation while making async calls to validate the form (see flutter/issues/9688 for details of this problem). For example, without leaving the form, this async form validation method can be used to validate a new user name against existing names in a database while signing up.
https://pub.dartlang.org/packages/modal_progress_hud
Here is the demo of the example provided with the package (with source code):
Example could be adapted to other modal progress indicator behaviour (like different animations, additional text in modal, etc..).
This is my solution with stack
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
final themeColor = new Color(0xfff5a623);
final primaryColor = new Color(0xff203152);
final greyColor = new Color(0xffaeaeae);
final greyColor2 = new Color(0xffE8E8E8);
class LoadindScreen extends StatefulWidget {
LoadindScreen({Key key, this.title}) : super(key: key);
final String title;
#override
LoginScreenState createState() => new LoginScreenState();
}
class LoginScreenState extends State<LoadindScreen> {
SharedPreferences prefs;
bool isLoading = false;
Future<Null> handleSignIn() async {
setState(() {
isLoading = true;
});
prefs = await SharedPreferences.getInstance();
var isLoadingFuture = Future.delayed(const Duration(seconds: 3), () {
return false;
});
isLoadingFuture.then((response) {
setState(() {
isLoading = response;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
),
centerTitle: true,
),
body: Stack(
children: <Widget>[
Center(
child: FlatButton(
onPressed: handleSignIn,
child: Text(
'SIGN IN WITH GOOGLE',
style: TextStyle(fontSize: 16.0),
),
color: Color(0xffdd4b39),
highlightColor: Color(0xffff7f7f),
splashColor: Colors.transparent,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(30.0, 15.0, 30.0, 15.0)),
),
// Loading
Positioned(
child: isLoading
? Container(
child: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
),
color: Colors.white.withOpacity(0.8),
)
: Container(),
),
],
));
}
}
You can do it for center transparent progress indicator
Future<Null> _submitDialog(BuildContext context) async {
return await showDialog<Null>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return SimpleDialog(
elevation: 0.0,
backgroundColor: Colors.transparent,
children: <Widget>[
Center(
child: CircularProgressIndicator(),
)
],
);
});
}
{
isloading? progressIos:Container()
progressIos(int i) {
return Container(
color: i == 1
? AppColors.liteBlack
: i == 2 ? AppColors.darkBlack : i == 3 ? AppColors.pinkBtn : '',
child: Center(child: CupertinoActivityIndicator()));
}
}
You can use FutureBuilder widget instead. This takes an argument which must be a Future. Then you can use a snapshot which is the state at the time being of the async call when loging in, once it ends the state of the async function return will be updated and the future builder will rebuild itself so you can then ask for the new state.
FutureBuilder(
future: myFutureFunction(),
builder: (context, AsyncSnapshot<List<item>> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
//Send the user to the next page.
},
);
Here you have an example on how to build a Future
Future<void> myFutureFunction() async{
await callToApi();}
Centered on screen:
Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [CircularProgressIndicator()])
])
class Loader extends StatefulWidget {
#override
State createState() => LoaderState();
}
class LoaderState extends State<Loader> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(milliseconds: 1200), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.elasticOut);
animation.addListener(() {
this.setState(() {});
});
animation.addStatusListener((AnimationStatus status) {});
controller.repeat();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
color: Colors.blue,
height: 3.0,
width: animation.value * 100.0,
),
Padding(
padding: EdgeInsets.only(bottom: 5.0),
),
Container(
color: Colors.blue[300],
height: 3.0,
width: animation.value * 75.0,
),
Padding(
padding: EdgeInsets.only(bottom: 5.0),
),
Container(
color: Colors.blue,
height: 3.0,
width: animation.value * 50.0,
)
],
);
}
}
Expanded(
child: Padding(
padding:
EdgeInsets.only(left: 20.0, right: 5.0, top:20.0),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FirstScreen()));
},
child: Container(
alignment: Alignment.center,
height: 45.0,
decoration: BoxDecoration(
color: Color(0xFF1976D2),
borderRadius: BorderRadius.circular(9.0)),
child: Text('Login',
style: TextStyle(
fontSize: 20.0, color: Colors.white))),
),
),
),
For your case, maybe it can be done by using showing a modal with a circle indicator. But I recommend using a simple plugin https://pub.dev/packages/flutter_easyloading.
The installation supper easy. Just run this flutter pub add flutter_easyloading in your terminal
Put this in you main.dart app
import 'package:flutter/material.dart';
import 'package:kunjungi_dokter/pages/welcome.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; // <- add this
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const Welcome(),
builder: EasyLoading.init(), // <- add this
);
}
}
To show the modal or the loading widget, in my case I show it in mya _login function in Login Screen:
import 'package:flutter_easyloading/flutter_easyloading.dart';
// ... other code
_login() async {
EasyLoading.show(status: 'loading...', maskType: EasyLoadingMaskType.black); // code to show modal with masking
var data = await LoginAPI.connectToAPI(
emailController.text, passwordController.text);
if (data.isError) {
EasyLoading.showError('Login Error: ' + data.message); // code to show modal without masking and auto close
} else {
await storage.write(key: 'token', value: data.token);
await storage.write(key: 'email', value: emailController.text);
EasyLoading.showSuccess('Login Success!'); // code to show modal without masking and auto close
Navigator.of(context)
.push(MaterialPageRoute(builder: ((context) => const Home())));
}
}
// ... other code
Tips, you can use this to close the modal:
EasyLoading.dismiss();
You will need a library for it
void onLoading() {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return GFLoader(
type: GFLoaderType.android,
);
},
);
}
and then use this function where you need in code
onLoading;