I want to make requests in the background every 30 seconds and foreground every 5 seconds, how to use Stream in this case?
here is my code:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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> with WidgetsBindingObserver {
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
// background proccess off
setPost(30);
} else {
// background proccess on
setPost(5);
}
}
Stream<int> setPost(int seconds) async* {
yield* Stream.periodic(Duration(seconds: seconds), (int i) {
post();
return i;
});
}
void post() async {
try {
var header = {
"Content-Type": "application/json",
};
var response = await http.post(url, headers: header);
if (response.statusCode == 200) {
print(response.body);
} else {
print(response.body);
}
} on SocketException catch (_) {
print('not connected');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(),
);
}
}
Just use Timer.pediodic:
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
Timer timer;
bool waitingForResponse = false;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); // Adding an observer
setTimer(false); // Setting a timer on init
}
#override
void dispose() {
timer?.cancel(); // Cancelling a timer on dispose
WidgetsBinding.instance.removeObserver(this); // Removing an observer
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
setTimer(state != AppLifecycleState.resumed);
}
void setTimer(bool isBackground) {
int delaySeconds = isBackground ? 5 : 3;
// Cancelling previous timer, if there was one, and creating a new one
timer?.cancel();
timer = Timer.periodic(Duration(seconds: delaySeconds), (t) async {
// Not sending a request, if waiting for response
if (!waitingForResponse) {
waitingForResponse = true;
await post();
waitingForResponse = false;
}
});
}
// Async method returns Future<> object
Future<void> post() async {
...
}
...
Related
is there a way to trigger the page navigation when a specific variable in provider class changes?
for example.
I want to navigate to login screen whenever the appLock variable becomes true otherwise do nothing
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
runApp(MultiProvider(providers: [
ChangeNotifierProvider(create: (_) => AppLockHelper()),
], child: const MyApp()));
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
context.read<AppLockHelper>().startThread();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
scaffoldBackgroundColor: kWhiteColor,
),
supportedLocales: L10n.all,
initialRoute: '/',
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
and this is my provider class
class AppLockHelper extends ChangeNotifier {
bool _appLocked = false;
bool get appLocked => _appLocked;
final _get = GenericGetApi();
Timer? timer;
void startThread() {
timer = Timer.periodic(
const Duration(seconds: 15), (Timer t) => getAppStatus());
}
void stopThread() {
timer?.cancel();
}
Future<void> getAppStatus() async {
var appStatusResult =
await _get.get(endpoint: EndPointsPool.getAppLockStatus);
appStatusResult.fold((l) {
_appLocked = false;
}, (r) {
AppLock appLockResult = AppLock.fromJson(r);
_appLocked = appLockResult.applocked;
});
notifyListeners();
}
}
Okay the solution would be add a listner in initState.
#override
void initState() {
super.initState();
final myNotifier = context.read<AppLockHelper>();
void listener() {
myNotifier.appLocked
? Navigator.pushNamedAndRemoveUntil(context, "/log-in", (_) => false)
: null;
}
myNotifier.addListener(listener);
}
I've the below code that is working fine, reading the csv data from url and printing the output:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:csv/csv.dart';
void fetchUserData() async {
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
print(rowsAsListOfValues);
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
fetchUserData();
}
#override
Widget build(BuildContext context) { // ... // }
}
Instead of getting the output printed, I need it to be returned into a variable, which I can display in y widget, I tried to do it as below:
Future<List<List<dynamic>>> fetchUserData() async { /// change
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
return rowsAsListOfValues; /// change
}
class _MyHomePageState extends State<MyHomePage> {
var rowsAsListOfValues; /// new
#override
void initState() {
super.initState();
rowsAsListOfValues = fetchUserData(); /// new
print(rowsAsListOfValues); /// new
}
#override
Widget build(BuildContext context) { // ... // }
}
But I got the output as I/flutter ( 7505): Instance of 'Future<List<List<dynamic>>>'
How can I fix it?
You need to switch from initState to didChangeDependency in this case. Because you need to await some process and you cant wait in initState. However you can wait like this
#override
void didChangeDependencies() async {
super.didChangeDependencies();
rowsAsListOfValues = await fetchUserData();
super.setState(() {}); // to update widget data
/// new
print(rowsAsListOfValues);
}
And this is the result
I/flutter (24313): [[vranches], [Dammam, 2], [Khobar, 3]]
You can wrap your code with Future.delayed() as given below.
#override
void initState() {
super.initState();
Future.delayed(Duration.zero,()async{
rowsAsListOfValues =await fetchUserData();
setState(() {});
print(rowsAsListOfValues); // this return correct value
});
print(rowsAsListOfValues); // this return null
}
Full Code
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:csv/csv.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomePage(title: 'Flutter Demo Home Page'),
);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_AppState createState() => _AppState();
}
Future<List<List<dynamic>>> fetchUserData() async {
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
return rowsAsListOfValues;
}
class _AppState extends State<HomePage> {
var rowsAsListOfValues;
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
rowsAsListOfValues = await fetchUserData();
setState(() {});
print(rowsAsListOfValues);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$rowsAsListOfValues',
),
],
),
),
);
}
}
The initState method is synchronous, and does not support async. I recommend the use of FutureBuilder, but you can also move the code to an async function.
FutureBuilder
import 'package:flutter/material.dart' show
Widget, FutureBuilder, AsyncSnapshot
;
class _MyHomePageState extends State<MyHomePage> {
static Future<void> fetchUserData() {
return Future().delayed(
Duration(seconds: 10),
() => 'loaded'
);
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([
fetchUserData()
]),
builder: (
BuildContext context,
AsyncSnapshot snapshot
) {
if (snapshot.hasData) {
return Text(snapshot.data);
}
return Text('loading...');
}
);
}
}
Async function
#override
void initState () {
super.initState();
(() async {
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
})();
}
OR
#override
void initState() {
super.initState();
initLoad();
}
void initLoad() async {
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
}
I feel more relaxed when using then() with async functions. You can try this:
fetchUserData().then((value) {
setState(() {
rowsAsListOfValues = value;
});
});
Or you can use await like this.
#override
void initState() async {
super.initState();
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
}
I have developed and app and the client want me to store the total time spend by the user inside the app
how can I achieve that
I have tried using this App_usage package in flutter but its showing me Star Activity error
if you guys have any solution please let me know
thanks in advance :)
Have some variable that tracks the start time and end/ pause time of the app and persist the difference. You will have to hook that up to the app lifecycle to listen to events such as pausing/ resuming the app. (e.g. How to handle onPause/onResume in Flutter App?)
Something like this:
class AppLifecycleReactor extends StatefulWidget {
const AppLifecycleReactor({Key key}) : super(key: key);
#override
_AppLifecycleReactorState createState() => _AppLifecycleReactorState();
}
class _AppLifecycleReactorState extends State<AppLifecycleReactor>
with WidgetsBindingObserver {
DateTime startTime;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
startTime = DateTime.now();
}
if (state == AppLifecycleState.detached ||
state == AppLifecycleState.paused) {
var usageTime = DateTime.now().difference(startTime);
// do whatever with the usageTime
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: MyContent(),
);
}
}
like Chris Marx said, you can use the counter to store usage time. and to handle the sync operation to server, you can use shared preferenceq to store data and when the app launched again you do sync(update) with the server.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(new HomePage());
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String? docId;
addTime() async {
docId = await TimeHomePageUsage.addUserStartTime();
}
#override
void initState() {
// TODO: implement initState
super.initState();
addTime();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
TimeHomePageUsage.addUserEndTime(docId);
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text('Home Page'),
),
);
}
}
class TimeHomePageUsage {
static Future<String?> addUserStartTime() async {
String? docId;
await FirebaseFirestore.instance
.collection('timeUsage')
.add({'startTime': DateTime.now().toString()})
.then((doc) => print("Start Time added ${docId = doc.id} "))
.catchError((error) => print("Failed to add Start Time: $error"));
return docId;
}
static Future<void> addUserEndTime(String? docId) async {
await FirebaseFirestore.instance
.collection('timeUsage')
.doc(docId)
.update({"endTime": DateTime.now().toString()})
.then((value) => print("End Time added "))
.catchError((error) => print("Failed to add End Time: $error"));
}
}
I want to subscribe to the purchaseUpdatedStream event after my app has initialised as I want to access localization text to display messages to the user if a purchase has failed. However I can't get the listen event to fire unless it's subscribed BEFORE the MaterialApp widget is built.
Working example:
class AppConfig extends InheritedWidget {
AppConfig({
#required this.appName,
#required Widget child,
#required this.prefs,
#required this.devMode
}) : super(child: child);
final String appName;
final SharedPreferences prefs;
final bool devMode;
static AppConfig of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var configuredApp = new AppConfig(
appName: 'app',
child: new MyApp(),
prefs: await SharedPreferences.getInstance(),
devMode: true,
);
InAppPurchaseConnection.enablePendingPurchases();
runApp(configuredApp);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
StreamSubscription<List<PurchaseDetails>> _subscription;
#override
void initState() {
Stream purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList, context);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
super.initState();
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList, BuildContext context) {
var config = AppConfig.of(context);
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
print('pending');
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
print('error');
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
print('purchased');
}
if (purchaseDetails.pendingCompletePurchase) {
print('complete');
await InAppPurchaseConnection.instance
.completePurchase(purchaseDetails);
}
}
});
}
#override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return MultiProvider(
providers: [
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: config.appName,
home: Scaffold(
body: SomeWidget(); // whack a button in this widget that triggers a product purchase
),
)
);
}
}
Non-working example:
class AppConfig extends InheritedWidget {
AppConfig({
#required this.appName,
#required Widget child,
#required this.prefs,
#required this.devMode
}) : super(child: child);
final String appName;
final SharedPreferences prefs;
final bool devMode;
static AppConfig of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var configuredApp = new AppConfig(
appName: 'app',
child: new AppScaffold(),
prefs: await SharedPreferences.getInstance(),
devMode: true,
);
InAppPurchaseConnection.enablePendingPurchases();
runApp(configuredApp);
}
class AppScaffold extends StatelessWidget {
#override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return MultiProvider(
providers: [
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: config.appName,
home: Scaffold(
body: MyApp()
),
)
);
}
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
StreamSubscription<List<PurchaseDetails>> _subscription;
#override
void initState() {
Stream purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList, context);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
super.initState();
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList, BuildContext context) {
var config = AppConfig.of(context);
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
print('pending');
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
print('error');
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
print('purchased');
}
if (purchaseDetails.pendingCompletePurchase) {
print('complete');
await InAppPurchaseConnection.instance
.completePurchase(purchaseDetails);
}
}
});
}
#override
Widget build(BuildContext context) {
return SomeWidget(); // whack a button in this widget that triggers a product purchase
}
}
Can anyone see if I'm going about this the wrong way and/or explain why this doesn't work?
My own fault - I was using Navigator.pushReplacement(...); elsewhere in the app which was triggering the dispose method on the child widget. Obvious now I think about it.
I am wanting to repeat the handleImages() method every five seconds but having an issue getting this to work.
import 'package:flutter/material.dart';
class CameraImageWidget extends StatefulWidget {
const CameraImageWidget({
Key key,
}) : super(key: key);
#override
_CameraImageWidgetState createState() => _CameraImageWidgetState();
}
class _CameraImageWidgetState extends State<CameraImageWidget> {
String handleImages() {
List<String> cameraImages = [
'assets/images/imageUpload.gif',
'assets/images/imageUploadGreen.gif',
];
String randomImage = (cameraImages..shuffle()).first;
return randomImage;
}
#override
Widget build(BuildContext context) {
return Image.asset(handleImages(), height: 450.0);
}
}
This should do the trick. Have not tested it tho.
class _CameraImageWidgetState extends State<CameraImageWidget> {
String _image;
#override
initState(){
handleImages();
}
void handleImages() async{
List<String> cameraImages = [
'assets/images/imageUpload.gif',
'assets/images/imageUploadGreen.gif',
];
setState(() {
_image = (cameraImages..shuffle()).first;
});
//Wait 5 seconds
await new Future.delayed(const Duration(seconds:5));
handleImages();
}
#override
Widget build(BuildContext context) {
return Image.asset(_image, height: 450.0);
}
}
import 'dart:async';
import 'package:flutter/material.dart';
class CameraImageWidget extends StatefulWidget {
const CameraImageWidget({
Key key,
}) : super(key: key);
#override
_CameraImageWidgetState createState() => _CameraImageWidgetState();
}
class _CameraImageWidgetState extends State<CameraImageWidget> {
String handleImages() {
String chosenImage;
List<String> cameraImages = [
'assets/images/imageUpload.gif',
'assets/images/imageUploadGreen.gif',
];
Timer.periodic(Duration(seconds: 10), (timer) {
setState(
() {
chosenImage = (cameraImages..shuffle()).first;
},
);
});
return chosenImage;
}
#override
Widget build(BuildContext context) {
return Image.asset(handleImages(), height: 400.0);
}
}
I made a dartpad.dev of the following code so that you can run it yourself. (however, instead of images, I am just showing the "asset text" every 5 seconds since it's on dartpad. You would just have to insert the text into the Image.asset using Image.asset(randomImage, height: 450.0);
Here's the dartpad: http://dartpad.dev/2af9c2d7c4b3436dfa55348bb4fb9dca
and here's the code:
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CameraImageWidget(),
);
}
}
class CameraImageWidget extends StatefulWidget {
const CameraImageWidget({
Key key,
}) : super(key: key);
#override
_CameraImageWidgetState createState() => _CameraImageWidgetState();
}
class _CameraImageWidgetState extends State<CameraImageWidget> {
static List<String> cameraImages = [
'assets/images/imageUpload.gif',
'assets/images/imageUploadGreen.gif',
];
String randomImage = (cameraImages..shuffle()).first;
int count = 0;
#override
initState() {
handleImages();
}
handleImages() {
Timer.periodic(Duration(seconds: 5), (timer) {
setState(() {
randomImage = (cameraImages..shuffle()).first;
print(randomImage);
count++;
print(count);
});
});
}
#override
Widget build(BuildContext context) {
// return Image.asset(handleImages(), height: 450.0);
return Text(randomImage); //
}
}