Getting location data in initState - flutter

I need to get location data which is used to calculate the distance between the user and other locations. This is on the app's home page and I don't want to do this every time the page loads, that's why I set up a timestamp and the location is grabbed only if five minutes pass.
What I have now is something like this in the home page:
LocationData _currentPosition;
#override
void initState() {
super.initState();
_getLocationData();
}
_getLocationData() async {
final content = Provider.of<Content>(context.read());
final _timestamp = DateTime.now().millisecondsSinceEpoch;
final _contentTimestamp = content.homeTimestamp;
if ((_contentTimestamp == null) ||
((_timestamp) - _contentTimestamp) >= 300000) {
try {
_locationData = await location.getLocation();
content.homeTimestamp = _timestamp;
setState(() {
_currentPosition = _locationData;
});
} on Exception catch (exception) {
print(exception);
} catch (error) {
print(error);
}
}
}
And I store the timestamp in Provider because I want it to persist when the user leaves the home page and returns. Not sure how to set it up without notifyListeners()
int _homeTimestamp;
int get homeTimestamp => _homeTimestamp;
set homeTimestamp(int newValue) {
_homeTimestamp = newValue;
notifyListeners();
}
My problem is that sometimes the location doesn't get stored and the page doesn't load. Is there a better way to do this?
I was thinking of adding a FutureBuilder in the body, but that would mean that the location will be retrieved every time the user loads the page, or I can just do the timestamp check in the body and not load the FutureBuilder all the time, but that doesn't seem right.
body: _currentPosition == null
? Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 20.0,
width: 20.0,
child: CircularProgressIndicator(),
),
SizedBox(
width: 10.0,
),
Text("Getting your location..."),
],
),
)
: Column(
...

use shared preference to check if the _getLocationData() method is called and use Timer.periodic(); method to call the function every five minutes.

Related

What kind of changes does "ref.watch(FutureProvider)" detect?

I'm really confusing that what is ref.watch(authUserFutureProvider);//here below detecting.
When final user = FirebaseAuth.instance.currentUser; // here set appropriately, ref.watch(authUserFutureProvider);//here detect the changes in the provider.
However, if I intentionally make user == null with FirebaseAuth.instance.signOut(); and execute authEmail(ref) with ref.refresh(authEmailFutureProvider);, ref.watch(authUserFutureProvider);//here do not detect any changes and nothing has been done in //do something when ref.watch() detected changes
I wonder why this happen and what kind of changes in FutureProvider should be detected.
Thanks for your helpful advises.
Future<bool> authEmail(ref) async {
try {
final user = FirebaseAuth.instance.currentUser; // here
await user?.sendEmailVerification();
return true;
}on FirebaseAuthException catch (e){
//do somthing
return false
}
//Declared Globally
final authEmailFutureProvider = FutureProvider<bool>((ref) async => authEmail(ref),);
class Test extends ConsumerWidget {
final authC = ref.watch(authUserFutureProvider);//here
authC.when(
//do something when ref.watch() detected changes
)
}
return Scaffold(
body: Padding(
//omit
Align(
child: SizedBox(
width: double.infinity,
child: TextButton(
onPressed: () {
ref.refresh(authEmailFutureProvider);
}
)
//omit
)

Firestore Data Update the Page Refresh automatically

I use listen() in retrieving data from Firestore. I am also using setData and merge: true to update the data in Firestore. When there is an update in the Firestore it will automatically refresh the whole page. How can I stop that from doing so. I want it to change the value but not refresh the page. Is it possible? Any documentation or sample code is welcome. Thank you in advance.
Update
This is my DropDownButton:
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DropdownButton(
items: routeName.map((value) {
return DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
underline: SizedBox(height: 0,),
onChanged: (value) => setState((){
selectedItem = value;
polylineCoordinates.clear();
getRoute();
}),
hint: new Text ("Select a Route"),
value: selectedItem,
)
],
),),
This is where I add in the value in the DropDownButton:
List <String> routeName = [];
Future <void> getRouteName() async{
Firestore.instance.collection('routes').snapshots().listen((RouteData) async {
routeName.clear();
if(RouteData.documents.isNotEmpty){
for (int i = 0; i < RouteData.documents.length; i++){
if(i == 0){
routeName.add("No Route");
}
routeName.add(RouteData.documents[i].documentID);
}
}
setState(() {
});
});
}
This is my initState()
#override
void initState(){
geoService.getCurrentLocation().listen((position){
centerScreen(position);
});
geoService.getCurrentLocation().listen((position) {
_addGeoPoint(position);
});
getMarker();
getRouteName();
retrieveRoute();
super.initState();
}
This is where things go wrong:
Future <void> getRoute() async{
if (selectedItem == "No Route"){
var user = await FirebaseAuth.instance.currentUser();
Firestore.instance.collection('user').document(user.email).setData(
{"Route": "No Route"}, merge: true);
}
var user = await FirebaseAuth.instance.currentUser();
Firestore.instance.collection('user').document(user.email).setData(
{"Route": selectedItem}, merge: true);
}
Because I want it to update to the Firestore whenever I update the value in the DropDownButton, but after the update it will refresh my page and I don't want it to refresh the whole page. I just want it to save to Firestore and update at the DropDownButton only.
If you want to listen to a Stream just one time, you may use the first operator on it:
Firestore.instance.collection('routes').snapshots().first.then(() => {
// this will be called juste once
});
The problem comes from your getRouteName method.
Future <void> getRouteName() async{
Firestore.instance.collection('routes').snapshots().listen((RouteData) async {
routeName.clear();
if(RouteData.documents.isNotEmpty){
for (int i = 0; i < RouteData.documents.length; i++){
if(i == 0){
routeName.add("No Route");
}
routeName.add(RouteData.documents[i].documentID);
}
}
setState(() {
});
});
}
}
Every time you call setState, it will cause the UI to be repainted. Since you use listen to get called for any changed to routes, and call setState() for every such change, any change to routes will repaint the UI.
If you don't want to repaint the UI altogether, remove the call to setState(). If you want to repaint the UI only in certain conditions, wrap the call to setState() in those conditions.

setState not causing UI to refresh

Using a stateful widget, I have this build function:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: [
Icon(MdiIcons.atom),
Text("Message Channel")
],
)
),
body: compileWidget(context),
bottomSheet: Row(
children: [
Expanded(
child: DefaultTextFormField(true, null, hintText: "Message ...", controller: messageField)
),
IconButton(
icon: Icon(Icons.send),
onPressed: onSendPressed
)
],
),
);
}
As you can see in the body, there is a compileWidget function defined as:
FutureBuilder<Widget> compileWidget(BuildContext context) {
return FutureBuilder(
future: createWidget(context),
builder: (context, snapshot) {
if (snapshot.hasData) {
print("DONE loading widget ");
return snapshot.data;
} else {
return Container(
child: Center(
child: CircularProgressIndicator()
),
);
}
}
);
}
In turn, the createWidget function looks like this:
List<Widget> bubbles;
Future<Widget> createWidget(BuildContext context) async {
await updateChatList();
// when working, return listview w/ children of bubbles
return Padding(
padding: EdgeInsets.all(10),
child: ListView(
children: this.bubbles,
),
);
}
// update chat list below
Future<void> updateChatList({Message message}) async {
if (this.bubbles != null) {
if (message != null) {
print("Pushing new notification into chat ...");
this.bubbles.add(bubbleFrom(message));
} else {
print("Update called, but nothing to do");
}
} else {
var initMessages = await Message.getMessagesBetween(this.widget.implicatedCnac.implicatedCid, this.widget.peerNac.peerCid);
print("Loaded ${initMessages.length} messages between ${this.widget.implicatedCnac.implicatedCid} and ${this.widget.peerNac.peerCid}");
// create init bubble list
this.bubbles = initMessages.map((e) {
print("Adding bubble $e");
return bubbleFrom(e);
}).toList();
print("Done loading bubbles ...");
}
}
The chat bubbles populate the screen in good order. However, once a new message comes-in and is received by:
StreamSubscription<Message> listener;
#override
void initState() {
super.initState();
this.listener = Utils.broadcaster.stream.stream.listen((message) async {
print("{${this.widget.implicatedCnac.implicatedCid} <=> ${this.widget.peerNac.peerCid} streamer received possible message ...");
if (this.widget.implicatedCnac.implicatedCid == message.implicatedCid && this.widget.peerNac.peerCid == message.peerCid) {
print("Message meant for this screen!");
await this.updateChatList(message: message);
setState(() {});
}
});
}
The "message meant for this screen!" prints, then within updateChatList, "Pushing new notification into chat ..." prints, implying that the bubble gets added to the array. Finally, setState is called, yet, the additional bubble doesn't get rendered. What might be going wrong here?
First of all, let me explain how setState works according to this link.
Whenever you change the internal state of a State object, make the change in a function that you pass to setState. Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.
In your case, I would say it's just a convention. You can define a message object in your stateful class and update it inside the listener and your setstate like this:
setState(() { this.message = message; });
Caution:
Before any changes, you need to check this question
Usage of FutureBuilder with setState
Because using setstete with FutureBuilder can make an infinite loop in StatefulWidget which reduces performance and flexibility. Probably, you should change your approach to design your screen because FutureBuilder automatically updates the state each time tries to fetch data.
Update:
There is a better solution for your problem. Because you are using a stream to read messages, you can use StreamBuilder instead of the listener and FutureBuilder. It brings less implementation and more reliability for services that provide a stream of data. Every time the StreamBuilder receives a message from the Stream, it will display whatever you want with the snapshot of data.
You need to ensure that the ListView has a UniqueKey:
ScrollController _scrollController = ScrollController();
Stream<Widget> messageStream;
#override
void initState() {
super.initState();
// use RxDart to merge 2 streams into one stream
this.messageStream = Rx.merge<Message>([Utils.broadcaster.stream.stream.where((message) => this.widget.implicatedCnac.implicatedCid == message.implicatedCid && this.widget.peerNac.peerCid == message.peerCid), this.initStream()]).map((message) {
print("[MERGE] stream recv $message");
this.widget.bubbles.add(bubbleFrom(message));
// this line below to ensure that, as new items get added, the listview scrolls to the bottom
this._scrollController = _scrollController.hasClients ? ScrollController(initialScrollOffset: _scrollController.position.maxScrollExtent) : ScrollController();
return ListView(
key: UniqueKey(),
controller: this._scrollController,
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
padding: EdgeInsets.only(top: 10, left: 10, right: 10, bottom: 50),
children: this.widget.bubbles,
);
});
}
Note: As per Erfan's directions, the context is best handled with a StreamBuilder. As such, I changed to a StreamBuilder, thus reducing the amount of code needed

Is there a way I can use Future values in Dart to fill Textfields?

I've been learning Dart with flutter and was creating Weather App for practices , I managed to successfully create the UI , I used Dark Sky weather API since it has 7 day weather forecast but instead of taking the long road of decoding json and models , I used Dark Sky Library.
Here's the code for the API
Future<Null> getWeather() async {
Location location = Location();
await location.getCurrentPosition();
var darksky = DarkSkyWeather(
MY_API_KEY,
units: Units.SI,
language: Language.English,
);
var forecast = await darksky.getForecast(
location.latitude, location.longitude, excludes: [
Exclude.Hourly,
Exclude.Minutely,
Exclude.Alerts,
Exclude.Flags
]);
print(forecast.currently.temperature.round());
print(forecast.daily.data[0].temperatureMax);
}
I wanted to use the forecast variable outside the functions and to fill the text fields such the humidity temperature among other data in the package . How can I access it ? any help will be appreciated .
Thanks
Take a look at this code
class _MyHomePageState extends State<MyHomePage> {
String _data; // This holds the data to be shown
#override
void initState() {
_data = "Press the button"; // This is the initial data // Set it in initState because you are using a stateful widget
super.initState();
}
// Call this to set the new data
void setData() {
setState(() { // Remember to use setState() else the changes won't appear
_data = 'Hello World';
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Message',
),
Text(
'$_data', // See how the data is fed into this text widget
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: setData, // When I press the button, the new data is set
child: Icon(Icons.send),
),
);
}
}
Output:
Initially:
After pressing the button:
Do the same thing in your code.
// Your code modified a bit
// Make sure this is a stateful widget because you are changing values
double _tempNow; // Declare two vars inside the class
double _tempMax;
// Your initState
#override
void initState() {
_tempNow = 0; // Initial values
_tempMax = 0;
super.initState();
}
// Build method
#override
Widget build(BuildContext context) {
// Your layout Code.....
Text(
'Current: $_tempNow' // Here you set the tempNow
),
......
Text(
'Maximum: $_tempMax' // and tempMax in the text widgets
),
// End
}
Future<Null> getWeather() async {
Location location = Location();
await location.getCurrentPosition();
var darksky = DarkSkyWeather(
"8810b3633934b8c1975c51a2311dc1d0",
units: Units.SI,
language: Language.English,
);
var forecast = await darksky.getForecast(
location.latitude, location.longitude, excludes: [
Exclude.Hourly,
Exclude.Minutely,
Exclude.Alerts,
Exclude.Flags
]);
// Change the values here.
// After the data is fetched, The changes will be made automatically
setState(() {
_tempNow = forecast.currently.temperature.round();
_tempMax = forecast.daily.data[0].temperatureMax;
});
}
In quite a similar manner, you can use text fields too. In that case, You will need to use TextEditingController and set the text there but, it seems in your case, you need read-only text to show the information to the user so a simple Text widget is enough

Flutter infinite/long list - memory issue and stack overflow error

my use case is to create a list view of articles (each item have the same look, there could be huge amount of articles, e.g. > 10000). I tried with
- ListView with ListView.builder: it supposes only to render the item when the item is displayed
- ScrollController: to determine when to load the next items (pagination)
- then I use List to store the data fetched from restful API using http, by adding the data from http to the List instance
this approach is OK, but in case the user keeps on scrolling pages, the List instance will have more and more items, it can crash with stack Overflow error.
If I don't call List.addAll(), instead I assign the data fetched from api, like: list = data;
I have problem that when the user scroll up, he/she won't be able to see the previous items.
Is there a good approach to solve this? Thanks!
import 'package:flutter/material.dart';
import 'package:app/model.dart';
import 'package:app/components/item.dart';
abstract class PostListPage extends StatefulWidget {
final String head;
DealListPage(this.head);
}
abstract class PostListPageState<T extends PostListPage> extends State<PostListPage> {
final int MAX_PAGE = 2;
DealListPageState(String head) {
this.head = head;
}
final ScrollController scrollController = new ScrollController();
void doInitialize() {
page = 0;
try {
list.clear();
fetchNextPage();
}
catch(e) {
print("Error: " + e.toString());
}
}
#override
void initState() {
super.initState();
this.fetchNextPage();
scrollController.addListener(() {
double maxScroll = scrollController.position.maxScrollExtent;
double currentScroll = scrollController.position.pixels;
double delta = 200.0; // or something else..
if ( maxScroll - currentScroll <= delta) {
fetchNextPage();
}
});
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
void mergeNewResult(List<PostListItem> result) {
list.addAll(result);
}
Future fetchNextPage() async {
if (!isLoading && mounted) {
page++;
setState(() {
isLoading = true;
});
final List<PostListItem> result = await doFetchData(page);
setState(() {
if (result != null && result.length > 0) {
mergeNewResult(result);
} else {
//TODO show notification
}
isLoading = false;
});
}
}
Future doFetchData(final int page);
String head;
List<PostListItem> list = new List();
var isLoading = false;
int page = 0;
int pageSize = 20;
final int scrollThreshold = 10;
Widget buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
#override
Widget build(BuildContext context) {
ListView listView = ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (BuildContext context, int index) {
if (index == list.length) {
return buildProgressIndicator();
}
if (index > 0) {
return Column(
children: [Divider(), PostListItem(list[index])]
);
}
return PostListItem(list[index]);
},
controller: scrollController,
itemCount: list.length
);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(head),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
},
),
// action button
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () {
},
),
]
),
body: new RefreshIndicator(
onRefresh: handleRefresh,
child: listView
),
);
}
Future<Null> handleRefresh() async {
doInitialize();
return null;
}
}
in my case, when the list length is 600, I start to get stack overflow error like:
I/flutter ( 8842): Another exception was thrown: Stack Overflow
I/flutter ( 8842): Another exception was thrown: Stack Overflow
screen:
enter image description here
somehow flutter doesn't show any more details of the error.
I wrote some sample code for a related question about paginated scrolling, which you could check out.
I didn't implement cache invalidation there, but it would easily be extendable using something like the following in the getPodcast method to remove all items that are more than 100 indexes away from the current location:
for (key in _cache.keys) {
if (abs(key - index) > 100) {
_cache.remove(key);
}
}
An even more sophisticated implementation could take into consideration the scroll velocity and past user behavior to lay out a probability curve (or a simpler Gaussian curve) to fetch content more intelligently.