I am trying to implement a Column with a Text:
Column(
children: <Widget>[
Text('data from future function')
],
),
I can't get the data from initState() cause initState() it's only void
If I get data directly from the function
Text(function)
I get
instance of function
The function:
Future<double> calculate(int index) async {
LocData _getUser = await getLoc();
double uLat = _getUser.latitude;
double uLng = _getUser.latitude;
double pLat = parks[data].loc.lat;
double pLng = parks[data].loc.lng;
double dis = await Geolocator()
.distanceBetween(uLat , uLng, uLng , pLat );
return dis ;
}
Any idea what can i do to get this data from the function directly to the text wigdet?
There 2 ways to get data from a future.
Option #1:
(with 2 suboptions)
class MyWidgetState extends State<MyWidget> {
String _someAsyncData;
#override
void initState() {
super.initState();
// opt 1.
aDataFuture.then((val) {
setState(() {
_someAsyncdata = val;
});
});
// opt 2.
_setAsyncData(aDataFuture);
}
void _setAsyncData(Future<String> someFuture) async {
// void + async is considered a "fire and forget" call
// part of opt. 2
_someAsyncData = await someFuture;
// must trigger rebuild with setState
setState((){});
}
Widget build(BuildContext context) {
return _someAsyncData == null ? Container() : Text('$_someAsyncData');
}
}
Option #2
Use FutureBuilder
class MyWidget extends StatelessWidget {
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: _someFuture,
builder: (ctx, snapshot) {
// can also check for snapshot.hasData or snapshot.hasError to
// provide more user feedback eg.
if(snapshot.connectionState == ConnectionState.done)
return Text('${snapshot.data}');
return Text('No data available yet...');
}
);
}
}
Here is the full working code.
class _InfoPageState extends State<InfoPage> {
String _text = "";
#override
void initState() {
super.initState();
calculate(10).then((value) {
setState(() {
_text = value.toString();
});
});
}
Future<double> calculate(int index) async {
LocData _getUser = await getLoc();
double uLat = _getUser.latitude;
double uLng = _getUser.latitude;
double pLat = parks[data].loc.lat;
double pLng = parks[data].loc.lng;
double dis = await Geolocator().distanceBetween(uLat, userLng, uLng, pLat);
return dis;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(children: <Widget>[Text(_text)]),
);
}
}
Related
import 'package:flutter/cupertino.dart';
class FarmplaceScreen extends StatefulWidget {
const FarmplaceScreen({Key key}) : super(key: key);
#override
_FarmplaceScreenState createState() => _FarmplaceScreenState();
}
class _FarmplaceScreenState extends State<FarmplaceScreen>
with AutomaticKeepAliveClientMixin {
final _nativeAdController = NativeAdmobController();
int limit = 15;
DocumentSnapshot lastVisible;
bool _hasNext = true;
bool _isFetching = false;
bool needMore = false;
final List<DocumentSnapshot> allProducts = [];
var productFuture;
var _getProductFuture;
ScrollController _scrollController = new ScrollController();
#override
void initState() {
super.initState();
if(lastVisible == null) productFuture =getUsers();
_scrollController.addListener(() {
if(_scrollController.offset >= _scrollController.position.maxScrollExtent){
if(_hasNext){
productFuture = getUsers();
setState(() {
_isFetching = true;
});
}
}
});
}
Future <QuerySnapshot> getUsers() {
if(_isFetching) return Future.value();
final refUsers = FirebaseFirestore.instance.collection('product').orderBy('publishedDate').limit(15);
Future.value(refUsers.startAfterDocument(allProducts.last).get());
if(lastVisible == null){
return Future.value(refUsers.get());
}
else{
return Future.value(refUsers.startAfterDocument(lastVisible).get());
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
child: FutureBuilder<QuerySnapshot>(
future: productFuture,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return ErrorDisplay();
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Container(
child: Center(child: circularProgress()),
);
}
lastVisible = snapshot.data.docs[snapshot.data.docs.length-1];
if (snapshot.data.docs.length < 15){
_hasNext = false;
}
if (snapshot.connectionState == ConnectionState.waiting){
_isFetching = true;
}
if (snapshot.connectionState == ConnectionState.done){
_isFetching = false;
}
allProducts.addAll(snapshot.data.docs);
return new GridView.countBuilder();
},
)
);
}
}
#override
bool get wantKeepAlive => true;
}
Hello Folks,
I am trying to achieve pagination using flutter Future builder widget.
Situation:
I am able to load first 15 products using the method stated above.
The problem occurs when I try to load the next 15 products.
I get the next next 15 products in line but, the future builder widget rebuilds. Now, to avoid the rebuild I have tried to initialize the future (productFuture) in the initState, but it dosen't solve the problem.
I tried setting _getProductFuture = productFuture in the initstate and then using _getProductFuture as the future in the FutureBuilder widget. In this case the widget doesn't rebuild but, the first 15 products are repeated everytime I scroll to the bottom of the screen.
Please suggest how I can stop this unnecessary rebuild of the FutureBuilder widget.
FYI: AbdulRahmanAlHamali's solution on GitHub dosen't work in this case.
I try to use FutureBuilder in Flutter to wait ulti my initState is finished then buil the UI for the app.
But when the app is running, the screen keep rebuilding each time I press another button (the button does totally different thing).
Future loadUser() async {
String jsonString = await storage.read(key: "jwt");
final jsonResponse = json.decode(jsonString);
loggedUser = new LoggedUser.fromJson(jsonResponse);
print(loggedUser.token);
getProfile();
getJourneyByUserId()
.then((receivedList){
addRanges(receivedList);});
}
Future<List<Journey>>getJourneyByUserId() async {
var res = await http.get(
Uri.parse("$baseUrl/journeys/userid=${loggedUser.user.userId}"),
headers: {
'Content_Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ${loggedUser.token}',
},
);
if (res.statusCode == 200) {
print("Get journeys successfully");
}
var data = jsonDecode(res.body);
List idList = [];
for (var i in data) {
idList.add(i["journeyId"]);
}
for (var i in idList) {
var res = await http.get(
Uri.parse("$baseUrl/journeys/$i"),
);
var data = jsonDecode(res.body);
Journey userJourney = new Journey.fromJson(data);
setState(() {
journeyList.add(userJourney);
});
}
print("Journey ${journeyList.length}");
return journeyList;
}
addRanges(journeyList){
setState(() {
rangeList=[];
});
if (journeyList.isNotEmpty) {
for (var i in journeyList) {
DateTime startDate =
DateTime(i.startDate.year, i.startDate.month, i.startDate.day);
DateTime endDate =
DateTime(i.endDate.year, i.endDate.month, i.endDate.day);
setState(() {
rangeList.add(PickerDateRange(startDate, endDate));
});
}
}
print("Range ${rangeList.length}");
return rangeList;
}
returnRange() {
List<PickerDateRange> list = [];
for(int i =0; i<rangeList.length;i++){
list.add(rangeList[i]);
}
return list;
}
Future functionForBuilder() async {
return await returnRange();
}
//initState function
#override
void initState() {
super.initState();
loadUser();
functionForBuilder();
}
//build the UI
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("$_name's Profile",style: TextStyle(color: kColorPalette4),),
centerTitle: true,
),
body: Container(
child: FutureBuilder(
future: functionForBuilder(),
builder: (BuildContext context,AsyncSnapshot snapshot){
//here I set the condition for each case of snapshot
}
I have read some documents say that I should assign the functionForBuilder() to a Future variable when initState then use it in the future child of FutureBuilder. Example:
Future _future;
//initState function
#override
void initState() {
super.initState();
loadUser();
_future=functionForBuilder();
}
// then with the FutureBuilder
future: _future
With this way the screen is not rebuild anymore but my function returnRange() seems like not running as my expextation (I called the returnRange() once in the build() function).
Thanks in advance for your answer!
Whenever you assign to the _future variable again, you must do that inside a setState block, otherwise the widget will not rebuild with the new future.
For example:
void updateData() {
setState(() {
_future = functionForBuilder();
});
}
If you use FutureBuilder, it rebuild items again and again.
Try two ways:
Don't use `future: functionForBuilder(), comment it.
Remove FutureBuilder(), simply use Container().
And let me know any issue?
Code:
call your future in the initstate method not in the build as shown in the example.
class MyPage extends StatefulWidget { #override State<MyPage> createState() => _MyPageState(); } class _MyPageState extends State<MyPage> { // Declare a variable. late final Future<int> _future; #override void initState() { super.initState(); _future = _calculate(); // Assign your Future to it. } // This is your actual Future. Future<int> _calculate() => Future.delayed(Duration(seconds: 3), () => 42); #override Widget build(BuildContext context) { return Scaffold( body: FutureBuilder<int>( future: _future, // Use your variable here (not the actual Future) builder: (_, snapshot) { if (snapshot.hasData) return Text('Value = ${snapshot.data!}'); return Text('Loading...'); }, ), ); } }
I'm fetching this API https://rickandmortyapi.com/api/character and putting the data inside a Stream so I can infinite scroll over a Gridview of Cards with every character.
Fetching the first page with a FutureBuilder it works, but trying to use a StreamBuilder just doesn't update anything as if it wasn't receiving any data.
Here's the the Provider.dart
class CharacterProvider {
final _url = 'rickandmortyapi.com';
final _characterStream = StreamController<List<Character>>.broadcast();
List<Character> _characters = [];
int currentPage = 1;
Function(List<Character>) get characterSink => _characterStream.sink.add;
Stream<List<Character>> get characterStream => _characterStream.stream;
void dispose() {
_characterStream?.close();
}
Future<Map<String, dynamic>> fetchData(
String path, Map<String, dynamic> header) async {
print(header);
final response = await http.get(
Uri.https(_url, 'api/$path', header),
);
if (response.statusCode == 200) {
final results = jsonDecode(response.body);
return results;
} else {
throw Exception('Fallo al cargar personajes');
}
}
Future<List<Character>> fetchCharacters() async {
final path = 'character';
final header = {
'page': currentPage.toString(),
};
final data = await fetchData(path, header);
final characterFetched = Characters.fromJsonList(data['results']);
_characters.addAll(characterFetched.character);
characterSink(_characters);
if (currentPage < data['info']['pages']) {
currentPage++;
}
return characterFetched.character;
}
}
The stream of StreamBuilder in the widget is subscribed to characterStream but it is always on null.
class _CharacterCardsState extends State<CharacterCards> {
final _scrollController = ScrollController();
Future<List<Character>> _characters;
int cards;
bool loading;
#override
void initState() {
super.initState();
print('Cards: init');
_characters = initFetch();
loading = true;
cards = 6;
_scrollController.addListener(updateCards);
}
Future<List<Character>> initFetch() async {
final fetch = await CharacterProvider().fetchCharacters();
return fetch;
}
#override
Widget build(BuildContext context) {
CharacterProvider().fetchCharacters();
print('Cards: build');
return GridView.builder(
itemCount: cards,
controller: _scrollController,
itemBuilder: (context, index) {
return StreamBuilder(
stream: CharacterProvider().characterStream,
builder: (BuildContext context,
AsyncSnapshot<List<Character>> snapshot) {
if (snapshot.hasData) {
loading = false;
final character = snapshot.data;
return GestureDetector(
onTap: () {
cardView(context, character, index);
},
child: ofCard(character, index),
);
} else {
return ofLoading(widget.size);
}
},
);
});
}
On debug, the values added to the sink are non-null. The data is fetching correctly but the sink.add() doesn't seem to be working.
I believe you're trying to use provider package (that's why you named your class CharacterProvider() I think), either way the problem is you're not saving a reference of that class, you're creating them anew each time you call CharacterProvider().someMethod so the initFetch CharacterProvider().fetchCharacters() and the stream CharacterProvider().characterStream are not related
Just like your scrollController you should create a final characterProvider = CharacterProvider() and call it in all your methods that requires it
PS: don't call a future CharacterProvider().fetchCharacters(); inside build like that, it's an antipattern
Try this.
class _CharacterCardsState extends State<CharacterCards> {
final _scrollController = ScrollController();
Future<List<Character>> _characters;
int cards;
bool loading;
#override
void initState() {
super.initState();
_characters = CharacterProvider();
_characters.fetchCharacters();
loading = true;
cards = 6;
_scrollController.addListener(updateCards);
}
#override
void dispose(){
_characters.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: cards,
controller: _scrollController,
itemBuilder: (context, index) {
return StreamBuilder(
stream: _characters.characterStream,
builder: (BuildContext context,
AsyncSnapshot<List<Character>> snapshot) {
if (snapshot.hasData) {
setState(()=>loading=false);
final character = snapshot.data;
return GestureDetector(
onTap: () {
cardView(context, character, index);
},
child: ofCard(character, index),
);
} else {
return ofLoading(widget.size);
}
},
);
});
}
I don't know why you are putting streambuilder inside gridview but logically above code should work.
Say, I have 2 widgets, A and B, where B is nested inside A. Both widgets are wrapped using Consumer. However, only widget A is able to get latest values from the provider, whereas widget B remains as the initial state.
class WidgetA extends StatelessWidget {
Widget build(BuildContext context) {
final FooProvider fooProvider = Provider.of<FooProvider>(context, listen: false);
fooProvider.fetchData();
return Consumer<FooProvider>(
builder: (context, value, child) {
print(value.modelList[0].name); //able to get latest value whenever changes are made to FooProvider.
return GestureDetector(
onTap: () async {
foodProvider.fetchData();
return showDialog(
context: context,
builder: (BuildContext context) {
return WidgetB(); //NOTICE I'm calling WidgetB here
}
)
},
child: WidgetB(); //NOTICE I'm calling WidgetB here
);
}
)
}
}
class WidgetB extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer<FooProvider>(
builder: (context, value, child) {
print(value.modelList[0].name); //unable to get latest in showDialog
return Container();
}
)
}
}
EDIT The code for ChangeNotifier:
It's just a regular Provider doing its work.
List<FooModel> modelList = [];
bool isWithinTimeFrame = false;
Future<void> fetchData(email, token, url) async {
await Service(
email,
token,
).fetchCutOff(url).then((response) {
if (response.statusCode == 200) {
var jsonResponse = json.decode(response.body.toString());
bool isSuccess = jsonResponse["success"];
if (isSuccess) {
dynamic formattedResponse = jsonResponse["data"];
List<FooModel> modelList = formattedResponse
.map<FooModel>((json) => FooModel.fromJson(json))
.toList();
setModelList(modelList);
setIsWithinTimeFrame(computeTime(modelList));
} else {}
} else {}
});
}
void setModelList(value) {
modelList = value;
notifyListeners();
}
void setIsWithinTimeFrame(value) {
isWithinTimeFrame = value;
notifyListeners();
}
I'm brand new to Flutter / Dart and I'm trying to build a reusable infinite scroller with placeholder loading. The class is as follows:
import 'dart:async';
import 'package:flutter/material.dart';
class PagedScroller<T> extends StatefulWidget {
final int limit;
final Future<List<T>> Function(int, int) getDataFunction;
final Widget Function(T) renderFunction;
final Widget Function() renderPlaceholderFunction;
PagedScroller(
{#required this.limit,
#required this.getDataFunction,
#required this.renderFunction,
#required this.renderPlaceholderFunction});
#override
_PagedScrollerState<T> createState() => _PagedScrollerState<T>();
}
class _PagedScrollerState<T> extends State<PagedScroller> {
int _offset = 0;
int _lastDataLength = 1; // Init to one so the first call can happen
List<dynamic> _items = [];
Future<List<dynamic>> _future;
bool _isInitializing = false;
bool _isInitialized = false;
bool _isLoading = false;
ScrollController _controller =
ScrollController(initialScrollOffset: 0.0, keepScrollOffset: true);
_PagedScrollerState();
void _init() {
_isInitializing = true;
_reset();
_controller.addListener(() {
bool loadMore = false;
if (_controller.position.maxScrollExtent == double.infinity) {
loadMore = _controller.offset == _controller.position.maxScrollExtent;
} else {
loadMore =
_controller.offset >= _controller.position.maxScrollExtent * 0.85;
}
// Only load more if it's not currently loading and we're not on the last page
// _lastDataLength should be 0 if there are no more pages
if (loadMore && !_isLoading && _lastDataLength > 0) {
_offset += widget.limit;
_load();
}
});
_load();
_isInitializing = false;
_isInitialized = true;
}
void _reset() {
// Clear things array and reset inital get-things link (without paging)
setState(() {
_future = _clearThings();
});
// Reload things
// Reset to initial GET link
_offset = 0;
}
void _load() {
setState(() {
_future = _loadPlaceholders();
_future = _loadData();
});
}
Future<List<dynamic>> _clearThings() async {
_items.clear();
return Future.value(_items);
}
Future<List<dynamic>> _loadPlaceholders() async {
// Add 20 empty placeholders to represent stuff that's currently loading
for (var i = 0; i < widget.limit; i++) {
_items.add(_Placeholder());
}
return Future.value(_items);
}
List<dynamic> _getInitialPlaceholders() {
var placeholders = List<dynamic>();
for (var i = 0; i < widget.limit; i++) {
placeholders.add(_Placeholder());
}
return placeholders;
}
Future<List<dynamic>> _loadData() async {
_setLoading(true);
var data = await widget.getDataFunction(widget.limit, _offset);
// When loading data is done, remove any placeholders
_items.removeWhere((item) => item is _Placeholder);
// If 0 items were returned, it's probably the last page
_lastDataLength = data.length;
for (var item in data) {
_items.add(item);
}
_setLoading(false);
return Future.value(_items);
}
void _setLoading(bool isLoading) {
if (!mounted) {
return;
}
setState(() {
_isLoading = isLoading;
});
}
Future<void> _refreshThings() async {
_reset();
_load();
return Future;
}
#override
Widget build(BuildContext context) {
if (!_isInitializing && !_isInitialized) {
_init();
}
return FutureBuilder(
future: _future,
initialData: _getInitialPlaceholders(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
List<dynamic> loadedItems = snapshot.data;
return RefreshIndicator(
onRefresh: _refreshThings,
child: ListView.builder(
itemCount: loadedItems.length,
controller: _controller,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
var item = loadedItems[index];
if (item is _Placeholder) {
return widget.renderPlaceholderFunction();
} else if (item is T) {
// THIS IS THE LINE THAT FAILS
return widget.renderFunction(item);
}
return Text('Unknown item type');
},
),
);
}
return Container();
},
);
}
}
class _Placeholder {}
The line that fails above:
return widget.renderFunction(item);
Fails with the following:
type '(MyModel) => Widget' is not a subtype of type '(dynamic) => Widget'
I understand why this is happening. The compiler can't know that type T from my PagedScroller<T> is the same as type T from _PagedScrollerState<T>. As a result, Dart tries to be helpful and converts my callback function of type Widget Function(T) to Widget Function(dynamic).
I then figured "maybe I can fake it out" with the following since I know the T in PagedScroller<T> and _PagedScrollerState<T> are always the same:
var renderFunction = widget.renderFunction as Widget Function(T);
return renderFunction(item);
Interestingly, this gives me a warning:
Unnecessary cast.
Try removing the cast.
Yet it won't even run that line (crashes) with the following:
Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=BUG.md
Changing everything to dynamic works a charm, but I really don't want to lose the readability of generics here if I don't have to.
Despite extensive searching, I can't find the equivalent of C#'s Convert.ChangeType where you can provide types at runtime so I can just do the cast I want and be done with it.
This seems like a really simple thing to achieve, but I'm stuck.
You can consume the scroller with this simple main.dart copy/pasted:
import 'package:flutter/material.dart';
import 'package:minimal_repros/paged_scroller.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
Future<List<MyModel>> getDataFunction(int limit, int offset) async {
var myModels = List<MyModel>();
// Simulate API call
await Future.delayed(Duration(milliseconds: 1000));
for (int i = 0; i < limit; i++) {
var myModel = MyModel();
myModel.count = i + offset;
myModel.firstName = 'Bob';
myModels.add(myModel);
}
return myModels;
}
Widget renderFunction(MyModel myModel) {
return Text(myModel.firstName);
}
Widget renderPlaceholderFunction() {
return Text('Loading');
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: PagedScroller(
getDataFunction: getDataFunction,
renderFunction: renderFunction,
renderPlaceholderFunction: renderPlaceholderFunction,
limit: 20));
}
}
class MyModel {
int count;
String firstName;
}
In the declaration of your State class, you forgot to specify the generic parameter of the widget.
Instead of:
class _PagedScrollerState<T> extends State<PagedScroller> {
do:
class _PagedScrollerState<T> extends State<PagedScroller<T>> {