I'm trying to change the state of isSyncing then rebuild the widget with set state once await api.fetchProducts() is completed. api.fetchProducts() is what i used to fetch from API then store local using sqflite.
I tried using cloudSyn.then() but it wont work.
class SyncProgress extends StatefulWidget {
#override
_SyncProgressState createState() => _SyncProgressState();
}
class _SyncProgressState extends State<SyncProgress> {
bool isSyncing = true;
String progressString = 'Syncing your data....';
final db = DatabaseHelper();
final bloc = ProductBloc();
#override
void initState() {
super.initState();
}
Future cloudSync() async{
await api.fetchProducts();
//Here is the challenge
setState(() {
isSyncing = false;
progressString = 'Syncing complete....';
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: isSyncing ? _indicateProgress() : _syncDone()
);
}
Widget _indicateProgress(){
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(),
SizedBox(height: 50.0,),
Text(progressString, style: TextStyle(
fontSize: 16.0,
),),
],
),
);
}
_syncDone(){
print('Syncing completed');
//return Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage()));
}
}
Use then to force setState function to execute only after fetchProducts() is finished:
Future cloudSync() async{
await api.fetchProducts().then(
setState(() {
isSyncing = false;
progressString = 'Syncing complete....';
});
);
}
Related
I have this code that has the parent widget Homepage and the child widget CountryList. In CountryList, I have created a function that uses an API to get a list of countries. I felt like enabling a RefreshIndicator in the app, so I had to modify the Homepage widget and add GlobalKey to access getCountryData() function of CountryList widget. The RefreshIndicator has done its job well. But the problem now is that when I pull and use the RefreshIndicator in the app, the getCountryData() function is called, but even after showing all data in the list, the circular spinner doesn't go (shown in the screenshot).
So, could anyone please suggest me a way to make the spinner go?
The code of main.dart containing Homepage widget is given below:
import 'package:flutter/material.dart';
import 'country_list.dart';
GlobalKey<dynamic> globalKey = GlobalKey();
void main() => runApp(MaterialApp(home: Homepage()));
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("List of countries"), actions: <Widget>[
IconButton(icon: Icon(Icons.favorite), onPressed: (){},)
],),
body: RefreshIndicator(child: CountryList(key:globalKey), onRefresh: (){globalKey.currentState.getCountryData();},),
);
}
}
And the code of country_list.dart containing CountryList widget is:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:flutter_svg/flutter_svg.dart';
class CountryList extends StatefulWidget {
CountryList({Key key}) : super(key: key);
#override
_CountryListState createState() => _CountryListState();
}
class _CountryListState extends State<CountryList> {
List<dynamic> _countryData;
bool _loading = false;
#override
void initState() {
// TODO: implement initState
super.initState();
this.getCountryData();
}
Future<String> getCountryData() async {
setState(() {
_loading = true;
});
var response =
await http.get(Uri.encodeFull("https://restcountries.eu/rest/v2/all"));
var decodedResponse = json.decode(response.body);
setState(() {
_countryData = decodedResponse;
_loading = false;
});
}
#override
Widget build(BuildContext context) {
return _loading?Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[CircularProgressIndicator(), Padding(padding: EdgeInsets.all(5.0),), Text("Loading data...", style: TextStyle(fontSize: 20.0),)],)):ListView.builder(
itemCount: _countryData.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
leading: SvgPicture.network(_countryData[index]['flag'], width: 60.0,),
title: Text(_countryData[index]['name']),
trailing: IconButton(
icon: Icon(Icons.favorite_border),
onPressed: () {},
),
),
);
},
);
}
}
You need to add return here:
Future<String> getCountryData() async {
setState(() {
_loading = true;
});
var response =
await http.get(Uri.encodeFull("https://restcountries.eu/rest/v2/all"));
var decodedResponse = json.decode(response.body);
setState(() {
_countryData = decodedResponse;
_loading = false;
});
return 'success';
}
and here:
body: RefreshIndicator(
child: CountryList(key: globalKey),
onRefresh: () {
return globalKey.currentState.getCountryData();
},
),
The onRefresh callback is called. The callback is expected to update the scrollable's contents and then complete the Future it returns. The refresh indicator disappears after the callback's Future has completed, I think you should return Future<String> from getCountryData.
I got the below geolocation.dart file, that works perfectly as stand alone widget:
import 'package:flutter/material.dart';
import 'package:location/location.dart';
import 'package:flutter/services.dart';
import 'package:simple_permissions/simple_permissions.dart';
class LocationField extends StatefulWidget {
const LocationField({
this.fieldKey,
this.onSaved,
});
final Key fieldKey;
final FormFieldSetter<String> onSaved;
#override
_LocationFieldState createState() => _LocationFieldState();
}
class _LocationFieldState extends State<LocationField> {
Location _location = new Location();
final lat = TextEditingController();
final lon = TextEditingController();
// #override
// void initState() {
// super.initState();
// lat.addListener(_addLatValue);
// lon.addListener(_addLonValue);
//}
#override
Widget build(BuildContext context) {
return Row(
textDirection: TextDirection.rtl,
children: <Widget>[
FlatButton(
child: const Icon(Icons.my_location),
onPressed: () => _getLocation(),
),
Expanded(child: Column(
textDirection: TextDirection.ltr,
children: <Widget>[
TextFormField(
controller: lat,
decoration: InputDecoration(
prefixIcon: Text("Latittude: ")
),
),
TextFormField(
controller: lon,
decoration: InputDecoration(
prefixIcon: Text("Longitude: ")
),
)
])
)
],
);
}
_getLocation() async {
Map<String, double> location;
var error = null;
try {
await SimplePermissions.requestPermission(Permission.AccessFineLocation);
location = await _location.getLocation();
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
error = 'Permission denied';
} else if (e.code == 'PERMISSION_DENIED_NEVER_ASK') {
error =
'Permission denied - please ask the user to enable it from the app settings';
}
location = null;
}
print("error $error");
setState(() {
lat.text = ('${location["latitude"]}');
lon.text = ('${location["longitude"]}');
});
}
}
And display the below, at which the location coordinate appear upon clicking the location icon, as below:
I can also insert it as a widget in my main app, as:
class _SignUpPageState extends State<SignUpPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(),
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
LocationField(),
RaisedButton(
onPressed: signUp,
child: Text('Sign up'),
),
],
)
),
);
}
void signUp() {
// what to write here to get the geolocation points lat/lon printed?
}
My question, is: How can I get the geolocation points lat/lon printed upon clicking the signup button, how can I get the value of the 2 fields from the sub-widget?
In Flutter, passing state down the widget tree is quite easy using InheritedWidget & co., while passing data upwards actually involves some thinking.
Similar to the TextEditingControllers you're using, you could create a LocationController that holds the location data:
class LocationController {
Location _location = Location();
get location => _location;
set location(Location val) {
_location = val;
if (onChanged != null) _onChanged(val);
}
VoidCallback _onChanged;
}
This controller can then be passed to the LocationField like this:
class LocationField extends StatefulWidget {
LocationField({
this.fieldKey,
#required this.controller,
this.onSaved,
});
final Key fieldKey;
final LocationController controller;
final FormFieldSetter<String> onSaved;
#override
_LocationFieldState createState() => _LocationFieldState();
}
class _LocationFieldState extends State<LocationField> {
final lat = TextEditingController();
final lon = TextEditingController();
#override
void initState() {
super.initState();
widget.controller._onChanged = (location) => setState(() {
lat.text = ('${location["latitude"]}');
lon.text = ('${location["longitude"]}');
});
lat.addListener(_addLatValue);
lon.addListener(_addLonValue);
}
#override
Widget build(BuildContext context) { ... }
_getLocation() async {
String error;
try {
await SimplePermissions.requestPermission(Permission.AccessFineLocation);
widget.controller.location = await _location.getLocation();
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
error = 'Permission denied';
} else if (e.code == 'PERMISSION_DENIED_NEVER_ASK') {
error =
'Permission denied - please ask the user to enable it from the app settings';
}
location = null;
}
print("error $error");
}
}
Then, in your widget up the tree, you can access the controller to retrieve the location:
class _SignUpPageState extends State<SignUpPage> {
LocationController controller = LocationController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
LocationField(controller: controller),
RaisedButton(onPressed: signUp, child: Text('Sign up')),
],
)
),
);
}
void signUp() {
final location = controller.location;
}
}
An added benefit is that you could set the controller's location from the widget up in the tree and the LocationField will automatically rebuild to reflect that change.
Suppose I have a SingleChildScrollView, its content is read from a file:
singleChildScrollView(
padding: EdgeInsets.all(8.0),
child: nw Text(
getTextFromFile(), //<---read from file
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 19.0,
),
));
Future<String> getFileData(String path) async {
return await rootBundle.loadString(path);
}
Future<String> getTextFromFile() async {
return getFileData("test.txt");
}
I got the following error:
The argument type 'Future<String>' can't be assigned to the parameter
type 'String'.
How to solve the issue?
Using a FutureBuilder should solve your problem. I modified you code so you can see how to use it. initialData is not required.
#override
Widget build(BuildContext context) {
return new FutureBuilder(
future: getTextFromFile(),
initialData: "Loading text..",
builder: (BuildContext context, AsyncSnapshot<String> text) {
return new SingleChildScrollView(
padding: new EdgeInsets.all(8.0),
child: new Text(
text.data,
style: new TextStyle(
fontWeight: FontWeight.bold,
fontSize: 19.0,
),
));
});
}
Future<String> getFileData(String path) async {
return await new Future(() => "test text");
}
Future<String> getTextFromFile() async {
return getFileData("test.txt");
}
}
StatefulWidget can be used for this purpose.
Declare a member variable String _textFromFile = ""; in your State class and update its value on future resolve by using setState() method.
I called your getTextFromFile() method from the constructor, but you may call it from anywhere.
Running code:
import 'package:flutter/material.dart';
import 'dart:async';
class StatefullWidgetDemo extends StatefulWidget {
#override
_StatefulWidgetDemoState createState() {
return new _StatefulWidgetDemoState();
}
}
class _StatefulWidgetDemoState extends State<StatefullWidgetDemo> {
String _textFromFile = "";
_StatefulWidgetDemoState() {
getTextFromFile().then((val) => setState(() {
_textFromFile = val;
}));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Stateful Demo'),
),
body: new SingleChildScrollView(
padding: new EdgeInsets.all(8.0),
child: new Text(
_textFromFile,
style: new TextStyle(
fontWeight: FontWeight.bold,
fontSize: 19.0,
),
),
),
);
}
Future<String> getFileData(String path) async {
return "your data from file";
}
Future<String> getTextFromFile() async {
return await getFileData("test.txt");
}
}
Simple answer here=>
The class which calls the function:
#override
Widget build(BuildContext context) {
return new Scaffold(
child: FutureBuilder(
future: function(),
builder: (BuildContext context, AsyncSnapshot<String> text) {
return new Text(text.data);
});
)}
And the function:
Future<String> function() async {
return 'abc';
}
Here's a similar question:
flutter / dart error: The argument type 'Future<File>' can't be assigned to the parameter type 'File'
The solution proposed there is quite elegant and works properly. Where the IDE says it's expecting Type and not Future<Type>, put await in front of that argument
Another solution to get data on initialization would be to call getTextFromFile() in initState(), set state with new string and use that string in your widget:
String fileData = '';
Future<String> getFileData(String path) async {
return await rootBundle.loadString(path);
}
void getTextFromFile() async {
try {
String data = await getFileData("test.txt");
setState(() {
fileData = data;
});
} catch (ex) {
print(ex);
}
}
#override
void initState() {
super.initState();
getTextFromFile();
}
new singleChildScrollView(
padding: new EdgeInsets.all(8.0),
child: new Text(
fileData,
style: new TextStyle(
fontWeight: FontWeight.bold,
fontSize: 19.0,
),
));
I would like to add one more answer, as I tested other answers I got an error.
Moving the async part (plus setState) from the constructor to initState() solved that for me.
Enjoy
class TestAsyncInStateful extends StatefulWidget {
const TestAsyncInStateful({super.key});
#override
State<TestAsyncInStateful> createState() => _TestAsyncInStatefulState();
}
class _TestAsyncInStatefulState extends State<TestAsyncInStateful> {
#override
void initState() {
super.initState();
getTextFromServer().then(
(value) => setState(() {
textFromServer = value;
}),
);
}
String? textFromServer;
#override
Widget build(BuildContext context) {
return textFromServer == null? const SizedBox() :Text(textFromServer!);
}
}
I am using imagecarousel package for displaying images from the network. I want to keep onPressed function for images in the slide.
new ImageCarousel(
<ImageProvider>[
new NetworkImage('http://www.hilversum.ferraridealers.com/siteasset/ferraridealer/54f07ac8c35b6/961/420/selected/0/0/0/54f07ac8c35b6.jpg'),
new NetworkImage('http://auto.ferrari.com/en_EN/wp-content/uploads/sites/5/2017/08/ferrari-portofino-reveal-2017-featured-new.jpg'),
new NetworkImage('http://www.hilversum.ferraridealers.com/siteasset/ferraridealer/54f07ac8c35b6/961/420/selected/0/0/0/54f07ac8c35b6.jpg'),
],
interval: new Duration(seconds: 1),
)
After making some modifications to Image Carousel, I was able to implement click event (other events also possible). Here is the sample code.
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class ImageCarousel extends StatefulWidget {
final List<ImageProvider> imageProviders;
final double height;
final TargetPlatform platform;
final Duration interval;
final TabController tabController;
final BoxFit fit;
// Images will shrink according to the value of [height]
// If you prefer to use the Material or Cupertino style activity indicator set the [platform] parameter
// Set [interval] to let the carousel loop through each photo automatically
// Pinch to zoom will be turned on by default
ImageCarousel(this.imageProviders,
{this.height = 250.0, this.platform, this.interval, this.tabController, this.fit = BoxFit.cover});
#override
State createState() => new _ImageCarouselState();
}
TabController _tabController;
class _ImageCarouselState extends State<ImageCarousel> with SingleTickerProviderStateMixin {
#override
void initState() {
super.initState();
_tabController = widget.tabController ?? new TabController(vsync: this, length: widget.imageProviders.length);
if (widget.interval != null) {
new Timer.periodic(widget.interval, (_) {
_tabController.animateTo(_tabController.index == _tabController.length - 1 ? 0 : ++_tabController.index);
});
}
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new SizedBox(
height: widget.height,
child: new TabBarView(
controller: _tabController,
children: widget.imageProviders.map((ImageProvider provider) {
return new CarouselImageWidget(widget, provider, widget.fit, widget.height);
}).toList(),
),
);
}
}
class CarouselImageWidget extends StatefulWidget {
final ImageCarousel carousel;
final ImageProvider imageProvider;
final BoxFit fit;
final double height;
CarouselImageWidget(this.carousel, this.imageProvider, this.fit, this.height);
#override
State createState() => new _CarouselImageState();
}
class _CarouselImageState extends State<CarouselImageWidget> {
bool _loading = true;
Widget _getIndicator(TargetPlatform platform) {
if (platform == TargetPlatform.iOS) {
return new CupertinoActivityIndicator();
} else {
return new Container(
height: 40.0,
width: 40.0,
child: new CircularProgressIndicator(),
);
}
}
#override
void initState() {
super.initState();
widget.imageProvider.resolve(new ImageConfiguration()).addListener((i, b) {
if (mounted) {
setState(() {
_loading = false;
});
}
});
}
#override
Widget build(BuildContext context) {
return new Container(
height: widget.height,
child: _loading
? _getIndicator(widget.carousel.platform == null ? defaultTargetPlatform : widget.carousel.platform)
: new GestureDetector(
child: new Image(
image: widget.imageProvider,
fit: widget.fit,
),
onTap: () {
int index = int.parse(_tabController.index.toString());
switch(index){
//Implement you case here
case 0:
case 1:
case 2:
default:
print(_tabController.index.toString());
}
},
),
);
}
}
void main(){
runApp(new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Demo"),
),
body: new ImageCarousel(
<ImageProvider>[
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-2.jpg'),
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-10.jpg'),
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-4.jpg'),
],
interval: new Duration(seconds: 5),
)
),
));
}
Hope it helps..!!
I am trying to build a countdown widget. Currently, I got the structure to work. I only struggle with the countdown itself. I tried this approach using the countdown plugin:
class _Countdown extends State<Countdown> {
int val = 3;
void countdown(){
CountDown cd = new CountDown(new Duration(seconds: 4));
cd.stream.listen((Duration d) {
setState((){
val = d.inSeconds;
});
});
}
#override
build(BuildContext context){
countdown();
return new Scaffold(
body: new Container(
child: new Center(
child: new Text(val.toString(), style: new TextStyle(fontSize: 150.0)),
),
),
);
}
}
However, the value changes very weirdly and not smooth at all. It start twitching. Any other approach or fixes?
It sounds like you are trying to show an animated text widget that changes over time. I would use an AnimatedWidget with a StepTween to ensure that the countdown only shows integer values.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class Countdown extends AnimatedWidget {
Countdown({ Key key, this.animation }) : super(key: key, listenable: animation);
Animation<int> animation;
#override
build(BuildContext context){
return new Text(
animation.value.toString(),
style: new TextStyle(fontSize: 150.0),
);
}
}
class MyApp extends StatefulWidget {
State createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
AnimationController _controller;
static const int kStartValue = 4;
#override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: new Duration(seconds: kStartValue),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.play_arrow),
onPressed: () => _controller.forward(from: 0.0),
),
body: new Container(
child: new Center(
child: new Countdown(
animation: new StepTween(
begin: kStartValue,
end: 0,
).animate(_controller),
),
),
),
);
}
}
The countdown() method should be called from the initState() method of the State object.
class _CountdownState extends State<CountdownWidget> {
int val = 3;
CountDown cd;
#override
void initState() {
super.initState();
countdown();
}
...
Description of initState() from the Flutter docs:
The framework calls initState. Subclasses of State should override
initState to perform one-time initialization that depends on the
BuildContext or the widget, which are available as the context and
widget properties, respectively, when the initState method is called.
Here is a full working example:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:countdown/countdown.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Countdown Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new CountdownWidget();
}
}
class _CountdownState extends State<CountdownWidget> {
int val = 3;
CountDown cd;
#override
void initState() {
super.initState();
countdown();
}
void countdown(){
print("countdown() called");
cd = new CountDown(new Duration(seconds: 4));
StreamSubscription sub = cd.stream.listen(null);
sub.onDone(() {
print("Done");
});
sub.onData((Duration d) {
if (val == d.inSeconds) return;
print("onData: d.inSeconds=${d.inSeconds}");
setState((){
val = d.inSeconds;
});
});
}
#override
build(BuildContext context){
return new Scaffold(
body: new Container(
child: new Center(
child: new Text(val.toString(), style: new TextStyle(fontSize: 150.0)),
),
),
);
}
}
class CountdownWidget extends StatefulWidget {
#override
_CountdownState createState() => new _CountdownState();
}
based on #raju-bitter answer, alternative to use async/await on countdown stream
void countdown() async {
cd = new CountDown(new Duration(seconds:4));
await for (var v in cd.stream) {
setState(() => val = v.inSeconds);
}
}
Why not use a simple TweenAnimationBuilder its easy to use and you don't need to manage any stream controllers or worry about using streams and disposing them off etc;
TweenAnimationBuilder<double>(
duration: Duration(seconds: 10),
tween: Tween(begin: 100.0, end: 0.0),
onEnd: () {
print('Countdown ended');
},
builder: (BuildContext context, double value, Widget child) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Text('${value.toInt()}',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 40)));
}),
here's the dartpad example to playaround
output:
originally answered here
Countdown example using stream, not using setState(...) therefore its all stateless.
this borrow idea from example flutter_stream_friends
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:countdown/countdown.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
static String appTitle = "Count down";
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: appTitle,
theme: new ThemeData(
primarySwatch: Colors.purple,
),
home: new StreamBuilder(
stream: new CounterScreenStream(5),
builder: (context, snapshot) => buildHome(
context,
snapshot.hasData
// If our stream has delivered data, build our Widget properly
? snapshot.data
// If not, we pass through a dummy model to kick things off
: new Duration(seconds: 5),
appTitle)),
);
}
// The latest value of the CounterScreenModel from the CounterScreenStream is
// passed into the this version of the build function!
Widget buildHome(BuildContext context, Duration duration, String title) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
child: new Text(
'Count down ${ duration.inSeconds }',
),
),
);
}
}
class CounterScreenStream extends Stream<Duration> {
final Stream<Duration> _stream;
CounterScreenStream(int initialValue)
: this._stream = createStream(initialValue);
#override
StreamSubscription<Duration> listen(
void onData(Duration event),
{Function onError,
void onDone(),
bool cancelOnError}) =>
_stream.listen(onData,
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
// The method we use to create the stream that will continually deliver data
// to the `buildHome` method.
static Stream<Duration> createStream(int initialValue) {
var cd = new CountDown(new Duration(seconds: initialValue));
return cd.stream;
}
}
The difference from stateful is that reload the app will restart counting. When using stateful, in some cases, it may not restart when reload.