I want to show a 5 min timer in my app which does't stop even when the app is closed and show the time left in mm:ss format. How can I show the time?
Here's a very very rudimentary example of how such a timer could work which persistently stores the target time:
class TimerApp extends StatefulWidget {
#override
_TimerAppState createState() => _TimerAppState();
}
class _TimerAppState extends State<TimerApp> {
SharedPreferences prefs;
DateTime target;
String timeLeft = "";
bool running = true;
#override
void initState() async {
super.initState();
prefs = await SharedPreferences.getInstance();
target = DateTime.fromMillisecondsSinceEpoch(prefs.getInt('target'));
if (target == null || target < DateTime.now()) {
target = DateTime.now().add(Duration(minutes: 5));
}
executeTimer();
}
#override
void dispose() {
prefs.setInt('target', target.millisecondsSinceEpoch);
running = false;
super.dispose();
}
void executeTimer() async {
while (running) {
setState(() {
timeLeft = DateTime.now().isAfter(target)
? '5 min expired. Restart app to reset.'
: target.difference(DateTime.now()).toString();
});
await Future.delayed(Duration(seconds: 1), () {});
}
}
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: Text(timeLeft),
);
}
}
Note that this example is not very fleshed out; several features are missing and using a while (running) loop is probably not the most elegant solution.
Here are some more resources you could have a look at:
The shared_preferences package for saving state persistently.
This Fluttery egg timer tutorial that uses a more sophisticated form of state management.
Related
My Task is :
I have an list of orders in firebase in users collection ,
I want to get information of those orders which is in orders collection,
And moment a list of order is updated in the firebase users collection.
The orders list should be automatically updated to get the latest information from the orders collection.
But for that i have to wait until list of orders is fetched from the users collection and only then i can query from orders collection about those orders..
I am stuck here,
And i want to actually understand Getx's bindStream , ever(), and observable variables,and Obx() is used in widget , But what if it is normal variable and i want to listen to it's changes ,how to do that, because Obx() can be only used while you use Widget
So far my code:
controllers.dart
UtilityController utilityController = UtilityController.instance;
CartController cartController = CartController.instance;
OrderController orderController = OrderController.instance;
UserModel.dart
class UserModel {
String? uid;
String? email;
String? name;
bool? isAdmin;
String? password;
List<CartItemModel>? cart;
String? token;
List<String>? orders;
UserModel({this.uid, this.email, this.name, this.isAdmin, this.password, this.cart, this.token, this.orders});
UserModel.fromSnapshot(DocumentSnapshot snapshot) {
uid = snapshot.id;
name = snapshot['name'];
token = snapshot['token'];
cart = _convertCartItems(snapshot['cart'] ?? []);
orders = new List<String>.from(snapshot['orders']);
}
List<CartItemModel> _convertCartItems(List cartFomDb) {
List<CartItemModel> _result = [];
if (cartFomDb.length > 0) {
cartFomDb.forEach((element) {
_result.add(CartItemModel.fromMap(element));
});
}
return _result;
}
}
UtilityController.dart
class UtilityController extends GetxController {
static UtilityController instance = Get.find();
Rx<UserModel> userModel = UserModel().obs;
#override
void onReady() {
super.onReady();
getUserType();
userModel.bindStream(listenToUser());
}
Stream<UserModel> listenToUser() {
return FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser?.uid)
.snapshots()
.map((snapshot) => UserModel.fromSnapshot(snapshot));
}
OrderController.dart
class OrderController extends GetxController {
static OrderController instance = Get.find();
RxList<OrderModel> orders = RxList<OrderModel>([]);
#override
void onReady() {
super.onReady();
orders.bindStream(getAllOrders());
ever(utilityController.userModel, function); --> I am using this , but i know this is not the correct way
}
function(UserModel userModel) {
getAllOrders();
}
Stream<List<OrderModel>> getAllOrders() {
return FirebaseFirestore.instance
.collection("orders")
.where(FieldPath.documentId, whereIn: utilityController.userModel.value.orders)
.snapshots()
.map((query) => query.docs.map((item) => OrderModel.fromMap(item.data(), item.id)).toList());
}
}
The utilityController.userModel.value.orders is null !!! it's not yet loaded, so all the orders are fetched :(
And even if the orders are changed... But new orders are not fetched from the orders collection
How to get over this?
Consider using like this.
class OrderController extends GetxController {
static OrderController instance = Get.find();
final isLoading = true.obs;
final isDone = false.obs;
var orders = OrderModel([]).obs;
#override
void onInit() async {
await listenForOrders();
super.onInit();
}
Future listenForOrders() async {
isLoading.value = true;
isDone.value = false;
Stream<QuerySnapshot> _userOrders;
_userOrders = getAllOrders();
_userOrders.listen((QuerySnapshot query) {
if (query.docs.isNotEmpty) {
query.docs.forEach((element) {
orders.addIf(!orders.contains(element), OrderModel.fromDocumentSnapshot(element));
});
} else {
isDone.value = true;
}
isLoading.value = false;
});
}
Stream<QuerySnapshot> getAllOrders() {
return FirebaseFirestore.instance
.collection("orders")
.where(FieldPath.documentId, whereIn: utilityController.userModel.value.orders)
.snapshots();
}
}
The best approach is to use the worker functions provided by getx controller like:
ever - is called every time the Rx variable emits a new value.
everAll - Much like ever , but it takes a List of Rx values Called every time its variable is changed. That's it.
once - is called only the first time the variable has been changed.
I want to display my adBanner only on one page, nothing else.
Currently, when I click on my page and I go back instantly, my ad loaded, and displayed on my main page.
I partialy fix this with:
Second screen
#override
void initState() {
super.initState();
FirebaseAdMobService().showBannerAd();
}
#override
dispose() async {
FirebaseAdMobService().hideBannerAd();
super.dispose();
}
AdmobService
import 'package:firebase_admob/firebase_admob.dart';
import 'package:flutter/foundation.dart';
class FirebaseAdMobService {
static final FirebaseAdMobService _singleton = FirebaseAdMobService._internal();
FirebaseAdMobService._internal();
factory FirebaseAdMobService() {
return _singleton;
}
BannerAd _adBanner;
bool _hide = false;
init() async {
await FirebaseAdMob.instance.initialize(appId: "...");
}
static const MobileAdTargetingInfo targetingInfo = MobileAdTargetingInfo(
keywords: <String>['foo', 'bar'],
childDirected: true,
nonPersonalizedAds: true,
);
BannerAd _createBannerAd() {
return BannerAd(
adUnitId: kReleaseMode ? "..." : BannerAd.testAdUnitId,
size: AdSize.banner,
targetingInfo: targetingInfo,
listener: (MobileAdEvent event) {
print("BannerAd event $event");
},
);
}
void showBannerAd() {
_hide = false;
Future.delayed(const Duration(seconds: 2), () {
if (_hide) {
return;
}
if (_adBanner == null) {
_adBanner = _createBannerAd();
}
_adBanner.load().then((loaded) {
_adBanner.show(anchorType: AnchorType.bottom);
_hide = false;
});
});
}
void hideBannerAd() async {
_hide = true;
await _adBanner?.dispose();
_adBanner = null;
}
}
With this, my ad is displayed after 2 seconds, but if you time the ad display (press back just after 2 seconds), the ad will displayed on the main page... and with my UI, block my bottom tab control.
How to prevent my ad from appearing elsewhere than on my second screen?
Thanks
I am trying to set the value of a variable based on the return value of a field (bool) in Firestore.
So far, this is what I have come up with;
First I call the method here;
#override
void initState() {
super.initState();
getAdventureStatus();
}
And this is the method.
Future getAdventureStatus() async {
Firestore.instance
.collection('adventures')
.document(widget.currentUser.id)
.collection('user_adventures')
.where('adventure_active', isEqualTo: 'false');
setState(() {
adventureActive = true;
print('${adventureActive.toString()}');
});}
What am I doing wrong and what is the most pragmatic way of doing this?
I think you just remove the set state call and set the bool to true direct.
Future getAdventureStatus() async {
Firebase.instance.document()
...
adventureActive = true;
}
This question already has answers here:
async is snowballing to callers, can't make constructor async
(4 answers)
Closed 3 years ago.
Just kind of confused as to when I should use which? What are the differences?
Does async await not execute the next line of code in the function until done and does it get pulled out the general order of functions? If so what does then do, how does it differ?
If I wanted to make sure something was done before calling the method to get a value so it doesn't return a null which should I use?
For instance I wanted to get info from a database and then set a variable to that data as soon as the screen loads so i define that inside initState(),
#override
void initState() {
// TODO: implement initState
super.initState();
currentUser= new User();
currentUser.getInfo().then((_) =>setState(() { bio = currentUser.getBio(); print(bio); }));
}
getInfo is an async fucntion, I tried this but what ends up happening is it somehow prints null first and then later prints the actual bio which is called from inside the getinfo method. How do I switch the order?
UPDATE:
Here is the user class:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
class User {
final _firestore = Firestore.instance;
final _auth= FirebaseAuth.instance;
FirebaseUser loggedInUser;
String displayName;
String email;
String bio;
String photoUrl;
Future<void> getCurrentUser() async{
try{
final user= await _auth.currentUser();
if(user!=null){
loggedInUser=user;
email=loggedInUser.email;
}}
catch(e){
print(e);
}
}
Future<void> getInfo() async {
await getCurrentUser();
DocumentReference documentReference =
_firestore.collection("users").document("$email");
documentReference.get().then((DocumentSnapshot datasnapshot) {
if (datasnapshot.exists) {
displayName=datasnapshot.data['displayName'].toString();
bio=datasnapshot.data['bio'].toString();
print(bio);
}
else {
print("No such user");
}
});
}
User({String bio,String displayName}){
if(bio!=null){
this.bio= bio;
print(this.bio);
}
if(displayName!=null){
this.displayName = displayName;
}
}
void updateData({String bio, String displayName}){
if(bio!=null){
this.bio=bio;
print(this.bio);
}
if(displayName!=null){
this.displayName=displayName;
}
_firestore.collection('users').document('$email').setData({
'bio':this.bio,
'displayName':this.displayName
});
}
String getBio(){
return bio;
}
}
UPDATE :
changed getinfo to this and it worked now , dont really get why though:
Future<void> getInfo() async {
await getCurrentUser();
DocumentReference documentReference =
_firestore.collection("users").document("$email");
await documentReference.get().then((DocumentSnapshot datasnapshot) {
if (datasnapshot.exists) {
displayName=datasnapshot.data['displayName'].toString();
bio=datasnapshot.data['bio'].toString();
print(bio);
}
else {
print("No such user");
}
});
}
await is a keyword that can only be used in async method.
then() is a method.
Example:
Future<void> A() async {
await Future.delayed(_duration);
print("A");
}
void B() {
print("B");
}
void C() {
print("C");
}
with await
void withAwait() async {
await A();
B();
C();
}
/// Print A, B, C respectively
with then
void withThen() {
A().then((_) => B());
C();
}
/// Print C, A, B respectively
void withThen2() {
A().then((_) {
B();
C();
});
}
/// Print A, B, C respectively
State Variables :
var moviePhotos = [
"http://www.kiwithebeauty.com/wp-content/uploads/2017/11/BLACK-PANTHER-COLLAGE-KIWI-THE-BEAUTY-MOVIE-MARVEL-800x350.png",
"https://static-ssl.businessinsider.com/image/5a7085a97e7a35f10c8b479f-1000/blackpanthershuri.jpg",
"https://longreadsblog.files.wordpress.com/2018/02/black-panther.jpg?w=1680",
"https://uziiw38pmyg1ai60732c4011-wpengine.netdna-ssl.com/wp-content/dropzone/2018/02/black-panther.jpg",
"https://static2.srcdn.com/wp-content/uploads/2017/10/Black-Panther-Trailer-1.jpg?q=50&w=1000&h=500&fit=crop&dpr=1.5",
"https://cdn.guidingtech.com/imager/media/assets/BP-2_acdb3e4bb37d0e3bcc26c97591d3dd6b.jpg",
"https://cdn.guidingtech.com/imager/media/assets/BP-8_acdb3e4bb37d0e3bcc26c97591d3dd6b.jpg"
];
var bannerPosition = 0;
I want the below function to change the position in the array every 5 seconds by incrementation bannerPosition so that a new image renders on the app
testing() async {
while(true){
await new Future.delayed(const Duration(seconds : 5));
if (bannerPosition < moviePhotos.length){
print("Banner Position Pre");
print(bannerPosition);
setState(() {
bannerPosition = bannerPosition + 1;
});
print("Banner Position Post");
print(bannerPosition);
}
else{
setState(() {
bannerPosition = 0;
});
}
}
}
The "Future.delayed(const Duration(seconds : 5))" does not occur in an orderly fashion when I execute this code and it results in image rendering issues.
I don't know what you mean by 'does not occur in an orderly fashion'. While just looking at that I'd think it would work, except that I seem to remember there being something weird about using await in a loop. It might keep looping around and creating more and more calls to the delayed....
Instead, use a Timer. That way it handles the looping. I'd also advise saving a reference to the timer and stopping it in your state's dispose() function.
Here's a code example:
class ImageRotater extends StatefulWidget {
List<String> photos;
ImageRotater(this.photos);
#override
State<StatefulWidget> createState() => new ImageRotaterState();
}
class ImageRotaterState extends State<ImageRotater> {
int _pos = 0;
Timer _timer;
#override
void initState() {
_timer = Timer.periodic(new Duration(seconds: 5), () {
setState(() {
_pos = (_pos + 1) % widget.photos.length;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return new Image.network(
widget.photos[_pos],
gaplessPlayback: true,
);
}
#override
void dispose() {
_timer.cancel();
_timer = null;
super.dispose();
}
}
Note that there still might be some inconsistency the first time it goes through the photos because it is just loading them as it goes. The 'gaplessPlayback' flag should make the previous image stick around until the new one is fully loaded.
Improving on "rmtmckenzie" answer, you need to use Timer.periodic if you want to repeat this every 5 seconds. See below
#override
void initState() {
_timer = Timer.periodic(Duration(seconds: 5), (Timer t) {
setState(() {
_pos = (_pos + 1) % widget.photos.length;
});
});
super.initState();
}