I don't quite understand how you can update future in FutureBuilder by timer. I tried to create a timer and give it to the future, but it didn't work out and there was an error: type '_Timer' is not a subtype of the 'Future?'
my handler with a request:
Future<ObjectStateInfoModel> read(int id) async {
TransportResponse response = await transport.request(
'get',
RequestConfig(path: path + '($id)'),
TransportConfig(
headers: {},
));
ObjectStateInfoModel objectState = ObjectStateInfoModel.fromJson(response.data);
return objectState;
}
my FutureBuilder:
return FutureBuilder<ObjectStateInfoModel>(
future: logexpertClient.objectsState.read(object.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
final data = snapshot.data!;
on the advice of one of the commentators i converted FutureBuilder to StreamBuilder and created such a stream and then everything works correctly:
stream = Stream.periodic(const Duration(seconds: 5)).asyncMap((_) async {
return logexpertClient.objectsState.read(object.id);
});
Use refreshable_widget, which is built specifically for this.
https://pub.dev/packages/refreshable_widget
Flexible(
child: RefreshableWidget<num>(
initialValue: challenge.userParticipation!.donePercent,
refreshCall: () async {
final challenge =
await cadoo.getChallengeDetail(
id: widget.challengeId,
);
return challenge.userParticipation!.donePercent;
},
builder: (context, value) {
return DonePercentWidget(
percent: value,
);
},
),
),
Pass a refresh call and how often you want to refresh, widget will build whatever on builder method.
Related
I have a screen that loads shopping cart items in a ListView.builder:
Expanded(
child: RefreshIndicator(
onRefresh: refresh,
child: Container(
child: FutureBuilder(
future: loadData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<dynamic>? filteredList = snapshot.data as List;
...
The data are loaded using the function loadData()
Future<List<LineaCesta>> loadData() async {
await fetchLineasCesta(idCesta);
return fetchLineasCesta(idCesta);
}
Then, inside the item card, there are some buttons to add or remove product quantity.
Here you have the case for adding a new one:
onPressed: () async {
//añadir uno maas
final prefs = await SharedPreferences.getInstance();
idCesta = prefs.getString("cesta_id")!;
checkExistenciaCestaPoner( idCesta!,lineaCesta.producto,lineaCesta.precio,context);
print( "refrescando despues de añadir item");
});
});
}
Then there are called other functions that at the end are calling the function loadData() again.
The issue is that all made changes are not updated after calling loadData.
I need to leave the screen and load it again to get all data updated.
EDIT:
Future<List<LineaCesta>> fetchLineasCesta(String cesta) async {
String url = Constantes().URLProyecto+Constantes().APICarpeta+"get_lineas_cesta.php?cesta="+cesta;
final response = await http.get(Uri.parse(url));
return lineaCestaFromJson(response.body);
}
When you like to refresh the FutureBuilder, reassing the future variable. For this I will prefer creating a separate variable for FutureBuilder's future.
late Future<List<LineaCesta>> loadDateFuture = loadData();
And use
FutureBuilder<List<LineaCesta>>(
future: loadDateFuture,
Now to update, reassign the loadDateFuture
loadDateFuture = loadData(); // like this
You can check
Randal L. Schwartz's video
This is the function, I want to retrieve currentUser data.
getData() async {
User? user = await FirebaseAuth.instance.currentUser;
print(user?.displayName);
}
How to display the name on the screen
When I call the function in text widget i.e
Text(getData().toString()),
I get the following display Instance of 'Future'<'dynamic'>''
i'm a beginner in Flutter, please help!
Since the function getData is async, it's a Future and you can't use a future methods inside your tree widget directly
you can use it inside your widget tree using the widget FutureBuilder
FutureBuilder(
future: getData(),
builder: (context, snapshot){
if (!snapshot.hasData) return const SizedBox();
return Text(snapshot.data?.toString() ?? '');
}
also, you have to modify your method to make it return something,
Ex.:
getData() async {
User? user = await FirebaseAuth.instance.currentUser;
print(user?.displayName);
return user?.displayName;
}
UPDATE:
to access all the info you want from the User object, let your method return the whole object;
getData() async {
User? user = await FirebaseAuth.instance.currentUser;
print(user?.displayName);
return user;
}
and your FutureBuilder will be
FutureBuilder(
future: getData(),
builder: (context, snapshot){
if (!snapshot.hasData) return const SizedBox();
if (snapshot.data == null) return const Text('Current user is null');
return Column(
children: [
Text('Name: ${snapshot.data?.displayName}'),
Text('Email: ${snapshot.data?.email}'),
///Add any attribute you want to show..
]
);
}
getData() async {
User? user = await FirebaseAuth.instance.currentUser;
if(user!=null){
print(user.displayName);
print(user.email);}
}
this will wait for an async method to first complete then print
I was working on local storage in flutter using SQflite db and Used Future to extract the file from the db to display using ListTile but it does't update instantly like stream do when I insert a new value to the db.
// This method is from the database to get the tasks that has been entered!
{
Future<List<Model>> getTasks() async {
Database _db = await database();
List<Map<String, dynamic>> taskMap = await _db.query('tasks');
return List.generate(taskMap.length, (index) {
return Model(
id: taskMap[index]['id'],
name: taskMap[index]['name'],
fatherName: taskMap[index]['fatherName']);
});
}
}
```
// This is Future Builder to extract the data from the database
{
Expanded(
child: FutureBuilder(
future: _dbHelper.getTasks(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return ContactList(snapshot.data[index], index);
},
);
},
),
)
}
```
// This is the answer
{
// this is method in provider class to get the task inserted in the db
Future loadTaskList() async {
_isLoading = true;
notifyListeners();
_taskList = await db.getTasks();
_isLoading = false;
notifyListeners();
}
}
// call the provider class in the main.dart file like this
{
ChangeNotifierProvider(
create: (ctx) => InputData()..loadTaskList(),
),
}
// then just use Consumer or Provider when you access the methods.
**This work perfectly for me!**
I want to use await inside streambuilder. However, if you use async inside, you get an error. On the code below !!!!!!!! That's the part I want to solve. Thank you very much if I can tell you how.
class _MemoStreamState extends State<MemoStream> {
final _fireStore = Firestore.instance;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _fireStore
.collection(widget.logInUsrEmail)
.orderBy('id', descending: false)
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
final memos = snapshot.data.documents;
List<MemoMaterial> memoList = [];
for (var memo in memos) {
final memoDocumentID = memo.documentID;
final memoTitle = await PlatformStringCryptor().decrypt(memo.data['title'], _key); !!!!!!!!!!
final memoUsrID = memo.data['usrID'];
final memoUsrPW = memo.data['usrPW'];
final memoText = memo.data['text'];
final memoCreateTime = memo.data['createTime'];
final memoMaterial = MemoMaterial(
logInUsrEmail: widget.logInUsrEmail,
doc: memoDocumentID,
title: memoTitle,
usrID: memoUsrID,
usrPW: memoUsrPW,
text: memoText,
createTime: memoCreateTime,
);
memoList.add(memoMaterial);
}
return Expanded(
child: new ListView.builder(
You should do something like this :
Stream<List<MemoMaterial>> memosStream;
Future<MemoMaterial> generateMemoMaterial(Memo memo) async {
final memoTitle =
await PlatformStringCryptor().decrypt(memo.data['title'], _key);
return MemoMaterial(
logInUsrEmail: widget.logInUsrEmail,
doc: memo.documentID,
title: memoTitle,
usrID: memo.data['usrID'],
usrPW: memo.data['usrPW'],
text: memo.data['text'];,
createTime: memo.data['createTime'],
);
}
#override
void initState() {
memosStream = _fireStore
.collection(widget.logInUsrEmail)
.orderBy('id', descending: false)
.snapshots()
.asyncMap((memos) => Future.wait([for (var memo in memos) generateMemoMaterial(memo)]));
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<List<MemoMaterial>>(
stream: memosStream // Use memostream here
asyncMap() will "transform" every new set of Documents into a list of MemoMaterial, and emit this list into the stream when the action is performed.
Future.wait() allows to perform multiple async requests simultaneously.
You can do it using FutureBuilder inside StreamBuilder in following way.
Stream<List<int>> callme() async* {
yield [1, 2, 3, 4, 5, 6];
}
buildwidget() async {
await Future.delayed(Duration(seconds: 1));
return 1;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: StreamBuilder(
stream: callme(),
builder: (_, sna) {
if (sna.hasData) {
return FutureBuilder(
future: buildwidget(),
builder: (_, snap) {
if (snap.hasData) {
return ListView.builder(
itemCount: sna.data.length,
itemBuilder: (_, index) {
return Text("${sna.data[index]} and ${snap.data}");
},
);
} else {
return CircularProgressIndicator();
}
},
);
} else {
return CircularProgressIndicator();
}
}),
),
);
}
I will prefer to use Getx or Provider State management to Handle the UI if it depends on the async function.
Suppose you want to fetch data from firebase using StreamBuilder() which returns some docs which contains image links then you want to download these images and show from storage. Obviously downloading the image is async type of work. Then you will get error if you show the images with the links you get direct from StreamBuilder().
What you can do is set a variable in getx or provider to show or hide the image Widget. If the Image is being downloaded or not downloaded then set the variable to hide/show the image when the async type of function is completed.
I'm trying to redirect to login in case the token has been expired in Flutter
Trying to get the posts:
body: new Container(
padding: new EdgeInsets.only(bottom: 8.0),
color: Color(0xff2c3e4e),
child: FutureBuilder<List<Post>>(
future: api.getposts(),
builder: (context, snapshot) {
// doing stuff with response
}
)
)
getposts and catching the error:
Future<List<Post>> getposts() async {
url = 'url';
var response = await http.get(url,
headers: {HttpHeaders.authorizationHeader: 'bearer ' + token},
);
//part I can't understand how to get to work, how do I push? This gives error about context not being defined.
if (response.body.toString().contains('Token is Expired')) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
LoginScreen()),
);
}
}
So the question is, how do I use the Navigator correctly in such cases and I can redirect back to loginScreen in case the token has been expired and needs to be renewed? Like mentioned in the comment in the code example, the context gives me "undefined".
Is this even possible the way I am doing it or am I simply handling the whole check completely wrong?
Code should have single resonpsibility. Your getPost method are doing 2 things at the same time. You should break this function such that it either successfully get the the post, or throw exception, and its caller will handle the exception. Its caller btw must be within build method, because only build method has BuildContext context, something like this:
if (response.body.toString().contains('Token is Expired')) {
throw new Exception("Token is Expired") // you may want to implement different exception class
}
body: new Container(
padding: new EdgeInsets.only(bottom: 8.0),
color: Color(0xff2c3e4e),
child: FutureBuilder<List<Post>>(
future: api.getposts(),
builder: (context, snapshot) {
if (snapshot.hasError) {
// you might want to handle different exception, such as token expires, no network, server down, etc.
Navigator.push(
context,
MaterialPageRoute(builder: (context) => LoginScreen()),
);
return Container(); // return empty widget
}
if (snapshot.hasData) {
// do somthing with data
return Text('Some data here');
}
// has no data
return Text("Loading...");
}),
),
UPDATE
Thanks to #TruongSinh I got it figured out.
Followed his example and figured out the build navigator method which works:
if (snapshot.hasError) {
#override
void run() {
scheduleMicrotask(() {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => LoginScreen()),
);
});
}
run();
}
Update: added a package containing several guards like this
I did it with a StreamBuilder to react on change and be able to display a LoadingScreen when we don't know yet if the user is connected.
StreamBuilder authGuard = StreamBuilder(
stream: Auth.authState$,
builder: (context, snapshot) {
switch (snapshot.data) {
case AuthState.PENDING:
return LoadingScreen();
case AuthState.UNAUTHENTICATED:
return SignInScreen();
case AuthState.AUTHENTICATED:
return HomeScreen();
default:
return LoadingScreen();
}
},
);
So it will change screen depending on the AuthState:
return MaterialApp(
// ...
home: authGuard,
);
And my auth class
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:rxdart/rxdart.dart';
enum AuthState { PENDING, AUTHENTICATED, UNAUTHENTICATED }
class Auth {
static final FirebaseAuth _auth = FirebaseAuth.instance;
static final GoogleSignIn _googleSignIn = GoogleSignIn();
static Stream<AuthState> authState$ = FirebaseAuth.instance.onAuthStateChanged
.map((state) =>
state != null ? AuthState.AUTHENTICATED : AuthState.UNAUTHENTICATED)
.startWith(AuthState.PENDING);
static Future<FirebaseUser> signInWithGoogle() async {
// ...
}
}