How to periodically set state? - flutter

I am trying to build a countdown app for special days for example 11 d 2 h 30 m 23s to the new year but I can't reload my state every second so it just shows me the second that I loaded the page I don't know how to dynamically reload the page.
import 'package:flutter/material.dart';
class RopSayac extends StatefulWidget {
_RopSayacState createState() => _RopSayacState();
}
class _RopSayacState extends State<RopSayac> {
var now = DateTime.now().second.toString();
String asd(){
setState(() {
now = DateTime.now().second.toString();
});
return now;
}
#override
Widget build(BuildContext context) {
return Container(
child: new Text(asd()),
);
}
}
This is what I got and it doesn't reload time. I am pretty new on the flutter.

As pskink and Günter mentioned, use a Timer. You can even use the periodic constructor that would fit well your scenario.
Note you don't need the asd() function. When you call setState(), the build method will be called automatically passing the new now property value.
If you want, use initState to set an initial value and, as in this example, setup the Timer.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: 'Timer Periodic Demo', home: RopSayac());
}
}
class RopSayac extends StatefulWidget {
_RopSayacState createState() => _RopSayacState();
}
class _RopSayacState extends State<RopSayac> {
String _now;
Timer _everySecond;
#override
void initState() {
super.initState();
// sets first value
_now = DateTime.now().second.toString();
// defines a timer
_everySecond = Timer.periodic(Duration(seconds: 1), (Timer t) {
setState(() {
_now = DateTime.now().second.toString();
});
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: new Text(_now),
),
);
}
}

This recursive method should be enough for what you want. The seconds set as 1 will keep triggering setState each second, thus refreshing your widget tree.
void _timer() {
Future.delayed(Duration(seconds: 1)).then((_) {
setState(() {
print("1 second closer to NYE!");
// Anything else you want
});
_timer();
});
}

There's this excellent library called timer_builder. I think it'll help you out.
Example from the pub page:
import 'package:timer_builder/timer_builder.dart';
class ClockWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TimerBuilder.periodic(Duration(seconds: 1), //updates every second
builder: (context) {
return Text("${DateTime.now()}");
}
);
}
}

Here's what I do. In my case I'm polling a webservice in _liveUpdate()
void startUpdates(AppState appState) async {
await new Future.delayed(const Duration(milliseconds: 100));
while (true) {
_liveUpdate();
appState.setState(() {});
await new Future.delayed(const Duration(seconds : 15));
}

Related

Calling async event in flutter_bloc

I am trying to fetch data from API as soon as the flutter app loads but I am unable to achieve so
class MarketBloc extends Bloc<MarketListEvent, MarketListState> {
MarketBloc() : super(MarketLoading()) {
on<MarketSelectEvent>((event, emit) async {
emit(MarketLoading());
final data = await ApiCall().getData(event.value!);
globalData = data;
emit(MarketDataFetched(marDat: globalData.data, dealType: event.value));
});
}
}
I have called MarketLoading state as the initial state and I want to call MarketSelectEvent just after that but in the current code, action is required to do so and i want to achieve it without any action.
You have 2 options:
add an event from the UI as soon you instantiate the MarketBloc
MarketBloc()..add(MarketSelectEvent())
add an event in the initialization code
MarketBloc() : super(MarketLoading()) {
add(MarketSelectEvent());
}
You could do this with in the initState of whatever the first page is that your app loads.
class TestPage extends StatefulWidget {
#override
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
late MarketBloc marketBloc;
#override
void initState() {
super.initState();
marketBloc = BlocProvider.of<MarketBloc>(context);
marketBloc.add(MarketSelectEvent());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocBuilder<MarketBloc, MarketListState>(
builder: (context, state) {
if (state is MarketLoading) {
return Text('loading...');
}
if (state is MarketDataFetched) {
return ...your UI that contains data from API call
}
},
),
),
);
}
}

Flutter WorkManager Background Fetch Example With StateFull Widget

I have a function called control in the StateFull Widget. I want to run this function with WorkManager every 15 minutes.
How can I call the control function from the callbackDispatcher function?
I added a Stream statically to the Statefull widget and then listened to it but it didn't work.
HomeScreen.dart file
import 'package:flutter/material.dart';
import 'package:workmanager/workmanager.dart';
const taskKontrol = "control";
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Container();
}
#override
void initState() {
super.initState();
setupWorkManager();
}
void control() async
{
//... my code control is here
}
}
void setupWorkManager() async {
await Workmanager.initialize(callbackDispatcher, isInDebugMode: true);
Workmanager.registerPeriodicTask(taskKontrol, taskKontrol,
frequency: Duration(seconds: 10),
existingWorkPolicy: ExistingWorkPolicy.append
);
}
void callbackDispatcher() {
Workmanager.executeTask((taskName, inputData) async {
switch(taskName)
{
case taskKontrol:
// How can I call the control function from here?
print("control from workmanager");
break;
}
return Future.value(true);
});
}
For those who still looking for an answer:
From the official docs:
The callbackDispatcher needs to be either a static function or a top level function to be accessible as a Flutter entry point.
I had this same problem and I solved it by moving the function callbackDispatcher to the file: main.dart
Also, the code that initializes callbackDispatcher must be in main() before the App() widget loads.
To call your control code, create a class with static function control()
Note: You cannot call the widget's method from callbackDispatcher!
Reason: Widgets are UI bound. As long as the screen remains active, the widget that is visible remains active. Once you close the app or move on to next screen, the widgets' memory gets recycled. But this callbackDispatcher gets executed even when your app is closed. So, it has to be isolated from UI code.
Here's the code:
main.dart:
import 'package:flutter/material.dart';
import 'package:workmanager/workmanager.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
runApp(App());
}
void callbackDispatcher() {
Workmanager.executeTask((taskName, inputData) async {
switch(taskName)
{
case ScheduledTask.taskName:
ScheduledTask.control(); // calls your control code
break;
}
return Future.value(true);
});
}
class ScheduledTask {
const static String taskName = "control";
static void control() {
// add your control here
}
}
All you can do from HomeScreen widget is to call setupWorkManager() that schedules the task
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Container();
}
#override
void initState() {
super.initState();
setupWorkManager();
}
}
void setupWorkManager() async {
Workmanager.registerPeriodicTask(taskKontrol, taskKontrol,
frequency: Duration(minutes: 15),
existingWorkPolicy: ExistingWorkPolicy.append
);
}
Note: The minimum frequency for the recurring task is 15 minutes

Show timer progress on a CircularProgressIndicator in flutter

I'm using a RestartableTimer (subclass of Timer) as a countdown timer, to "kick people out" of a form after a certain duration.
I would like to display the progress of that timer, and I like the idea of a circular progress slowly filling up.
I'm not showing my code because I don't really have anything to show. I have a completely static progress indicator and a working timer, in a widget (stateful or stateless, whichever works best).
I face two issues and this is where I need help for :
I don't know how to check every x milliseconds for the timer progress. How can I do that? I don't need copy-pasta code, but more of "what object / which direction" should I go for?
The timer progress in ticks is not implemented (NotImplementedException) ; is there any way to have an equivalent somewhere else? That object works really well for me, except for that part.
Am I SOL or is there a way to make it?
There's nothing to be implemented in the getter tick, since RestartableTimer is not periodic. What you want is a much more complex thing, and RestartableTimer is not able to help you with that.
First, you need something to control the progress of the CircularProgressIndicator:
class ProgressController {
static const double smoothnessConstant = 250;
final Duration duration;
final Duration tickPeriod;
Timer _timer;
Timer _periodicTimer;
Stream<void> get progressStream => _progressController.stream;
StreamController<void> _progressController = StreamController<void>.broadcast();
Stream<void> get timeoutStream => _timeoutController.stream;
StreamController<void> _timeoutController = StreamController<void>.broadcast();
double get progress => _progress;
double _progress = 0;
ProgressController({#required this.duration})
: assert(duration != null),
tickPeriod = _calculateTickPeriod(duration);
void start() {
_timer = Timer(duration, () {
_cancelTimers();
_setProgressAndNotify(1);
_timeoutController.add(null);
});
_periodicTimer = Timer.periodic(
tickPeriod,
(Timer timer) {
double progress = _calculateProgress(timer);
_setProgressAndNotify(progress);
},
);
}
void restart() {
_cancelTimers();
start();
}
Future<void> dispose() async {
await _cancelStreams();
_cancelTimers();
}
double _calculateProgress(Timer timer) {
double progress = timer.tick / smoothnessConstant;
if (progress > 1) return 1;
if (progress < 0) return 0;
return progress;
}
void _setProgressAndNotify(double value) {
_progress = value;
_progressController.add(null);
}
Future<void> _cancelStreams() async {
if (!_progressController.isClosed) await _progressController.close();
if (!_timeoutController.isClosed) await _timeoutController.close();
}
void _cancelTimers() {
if (_timer?.isActive == true) _timer.cancel();
if (_periodicTimer?.isActive == true) _periodicTimer.cancel();
}
static Duration _calculateTickPeriod(Duration duration) {
double tickPeriodMs = duration.inMilliseconds / smoothnessConstant;
return Duration(milliseconds: tickPeriodMs.toInt());
}
}
Then you can implement a CircularProgressIndicator that listens to the Streams from ProgressController:
class RestartableCircularProgressIndicator extends StatefulWidget {
final ProgressController controller;
final VoidCallback onTimeout;
RestartableCircularProgressIndicator({
Key key,
#required this.controller,
this.onTimeout,
}) : assert(controller != null),
super(key: key);
#override
_RestartableCircularProgressIndicatorState createState() =>
_RestartableCircularProgressIndicatorState();
}
class _RestartableCircularProgressIndicatorState
extends State<RestartableCircularProgressIndicator> {
ProgressController get controller => widget.controller;
VoidCallback get onTimeout => widget.onTimeout;
#override
void initState() {
super.initState();
controller.progressStream.listen((_) => updateState());
controller.timeoutStream.listen((_) => onTimeout());
}
#override
Widget build(BuildContext context) {
return CircularProgressIndicator(
value: controller.progress,
);
}
void updateState() => setState(() {});
}
You can also pass some of the paramers of CircularProgressIndicator to RestartableCircularProgressIndicator, so you can customize it.
A usage example:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ProgressController controller;
#override
void initState() {
super.initState();
controller = ProgressController(
duration: Duration(seconds: 5),
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RestartableCircularProgressIndicator(
controller: controller,
onTimeout: () => print('timeout'),
),
RaisedButton(
onPressed: controller.start,
child: Text('Start'),
),
RaisedButton(
onPressed: controller.restart,
child: Text('Restart'),
),
],
),
),
),
);
}
}
I'll convert this into a library someday, but until then I cannot provide the tests and documentation to this code, so you have to study it if you want to understand what's going on here (I'm sorry...).

How to handle navigation using stream from inheritedWidget?

I'm using an inherited Widget to access a Bloc with some long running task (e.g. search).
I want to trigger the search on page 1 and continue to the next page when this is finished. Therefore I'm listening on a stream and wait for the result to happen and then navigate to the result page.
Now, due to using an inherited widget to access the Bloc I can't access the bloc with context.inheritFromWidgetOfExactType() during initState() and the exception as I read it, recommends doing this in didChangeDependencies().
Doing so this results in some weird behavior as the more often I go back and forth, the more often the stream I access fires which would lead to the second page beeing pushed multiple times. And this increases with each back and forth interaction. I don't understand why the stream why this is happening. Any insights here are welcome. As a workaround I keep a local variable _onSecondPage holding the state to avoid pushing several times to the second Page.
I found now How to call a method from InheritedWidget only once? which helps in my case and I could access the inherited widget through context.ancestorInheritedElementForWidgetOfExactType() and just listen to the stream and navigate to the second page directly from initState().
Then the stream behaves as I would expect, but the question is, does this have any other side effects, so I should rather get it working through listening on the stream in didChangeDependencides() ?
Code examples
My FirstPage widget listening in the didChangeDependencies() on the stream. Working, but I think I miss something. The more often i navigate from first to 2nd page, the second page would be pushed multiple times on the navigation stack if not keeping a local _onSecondPage variable.
#override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint("counter: $_counter -Did change dependencies called");
// This works the first time, after that going back and forth to the second screen is opened several times
BlocProvider.of(context).bloc.finished.stream.listen((bool isFinished) {
_handleRouting(isFinished);
});
}
void _handleRouting(bool isFinished) async {
if (isFinished && !_onSecondPage) {
_onSecondPage = true;
debugPrint("counter: $_counter - finished: $isFinished : ${DateTime.now().toIso8601String()} => NAVIGATE TO OTHER PAGE");
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
_onSecondPage = false;
} else {
debugPrint("counter: $_counter - finished: $isFinished : ${DateTime.now().toIso8601String()} => not finished, nothing to do now");
}
}
#override
void dispose() {
debugPrint("counter: $_counter - disposing my homepage State");
subscription?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder(
stream: BlocProvider.of(context).bloc.counter.stream,
initialData: 0,
builder: (context, snapshot) {
_counter = snapshot.data;
return Text(
"${snapshot.data}",
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
A simple Bloc faking some long running work
///Long Work Bloc
class LongWorkBloc {
final BehaviorSubject<bool> startLongWork = BehaviorSubject<bool>();
final BehaviorSubject<bool> finished = BehaviorSubject<bool>();
int _counter = 0;
final BehaviorSubject<int> counter = BehaviorSubject<int>();
LongWorkBloc() {
startLongWork.stream.listen((bool start) {
if (start) {
debugPrint("Start long running work");
Future.delayed(Duration(seconds: 1), () => {}).then((Map<dynamic, dynamic> reslut) {
_counter++;
counter.sink.add(_counter);
finished.sink.add(true);
finished.sink.add(false);
});
}
});
}
dispose() {
startLongWork?.close();
finished?.close();
counter?.close();
}
}
Better working code
If I however remove the code to access the inherited widget from didChangeDependencies() and listen to the stream in the initState() it seems to be working properly.
Here I get hold of the inherited widget holding the stream through context.ancestorInheritedElementForWidgetOfExactType()
Is this ok to do so? Or what would be a flutter best practice in this case?
#override
void initState() {
super.initState();
//this works, but I don't know if this is good practice or has any side effects?
BlocProvider p = context.ancestorInheritedElementForWidgetOfExactType(BlocProvider)?.widget;
if (p != null) {
p.bloc.finished.stream.listen((bool isFinished) {
_handleRouting(isFinished);
});
}
}
Personally, I have not found any reason not to listen to BLoC state streams in initState. As long as you remember to cancel your subscription on dispose
If your BlocProvider is making proper use of InheritedWidget you should not have a problem getting your value inside of initState.
like So
void initState() {
super.initState();
_counterBloc = BlocProvider.of(context);
_subscription = _counterBloc.stateStream.listen((state) {
if (state.total > 20) {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return TestPush();
}));
}
});
}
Here is an example of a nice BlocProvider that should work in any case
import 'package:flutter/widgets.dart';
import 'bloc_base.dart';
class BlocProvider<T extends BlocBase> extends StatefulWidget {
final T bloc;
final Widget child;
BlocProvider({
Key key,
#required this.child,
#required this.bloc,
}) : super(key: key);
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context) {
final type = _typeOf<_BlocProviderInherited<T>>();
_BlocProviderInherited<T> provider =
context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
return provider?.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<BlocBase>> {
#override
Widget build(BuildContext context) {
return _BlocProviderInherited<T>(
bloc: widget.bloc,
child: widget.child,
);
}
#override
void dispose() {
widget.bloc?.dispose();
super.dispose();
}
}
class _BlocProviderInherited<T> extends InheritedWidget {
final T bloc;
_BlocProviderInherited({
Key key,
#required Widget child,
#required this.bloc,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
... and finally the BLoC
import 'dart:async';
import 'bloc_base.dart';
abstract class CounterEventBase {
final int amount;
CounterEventBase({this.amount = 1});
}
class CounterIncrementEvent extends CounterEventBase {
CounterIncrementEvent({amount = 1}) : super(amount: amount);
}
class CounterDecrementEvent extends CounterEventBase {
CounterDecrementEvent({amount = 1}) : super(amount: amount);
}
class CounterState {
final int total;
CounterState(this.total);
}
class CounterBloc extends BlocBase {
CounterState _state = CounterState(0);
// Input Streams/Sinks
final _eventInController = StreamController<CounterEventBase>();
Sink<CounterEventBase> get events => _eventInController;
Stream<CounterEventBase> get _eventStream => _eventInController.stream;
// Output Streams/Sinks
final _stateOutController = StreamController<CounterState>.broadcast();
Sink<CounterState> get _states => _stateOutController;
Stream<CounterState> get stateStream => _stateOutController.stream;
// Subscriptions
final List<StreamSubscription> _subscriptions = [];
CounterBloc() {
_subscriptions.add(_eventStream.listen(_handleEvent));
}
_handleEvent(CounterEventBase event) async {
if (event is CounterIncrementEvent) {
_state = (CounterState(_state.total + event.amount));
} else if (event is CounterDecrementEvent) {
_state = (CounterState(_state.total - event.amount));
}
_states.add(_state);
}
#override
void dispose() {
_eventInController.close();
_stateOutController.close();
_subscriptions.forEach((StreamSubscription sub) => sub.cancel());
}
}

how to implement in app time out session in flutter

I want to log a user out after a specific amount time the user has not interacted with the app.
I've wrapped the whole child widget in GestureDetector().
Please suggest if this is the best optimised way of doing this.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
// duration reset's to a specific time
startTimeout([int milliseconds]) { return new Timer(duration, handleTimeout); }
},
child: new HomeWidget(),);
}
void handleTimeOut {
// Log User Out
}
}
You should cancel previous timers before initializing a new one
static Timer _sessionTimer;
#override
Widget build(BuildContext context) {
...
onTap: () {
_sessionTimer?.cancel();
// duration reset's to a specific time
_sessionTimer = new Timer(duration, handleTimeout);
},
If you need something for the web target then better setup a key-up and a mouse-click listener on your index.html's 'body' as follows.
...
<body id = 'myapp-main-content'>
...
Then implement the listeners, here is an example borrowed from Task Tracker (https://github.com/botorabi/TaskTracker/tree/master/src/flutter-app/TaskTracker/lib).
import 'dart:async';
import 'dart:html';
import 'package:TaskTracker/service/authstatus.dart';
import 'package:flutter/material.dart';
import 'config.dart';
import 'navigation.links.dart';
import 'service/service.login.dart';
/// Logout user after long inactivity period.
class SessionTimeoutHandler {
static const MAIN_CONTAINER_ID = 'myapp-main-content';
final GlobalKey<NavigatorState> _navigator;
Timer _sessionTimer;
int _timeoutInSeconds;
static DateTime _timeLeft;
SessionTimeoutHandler(this._navigator, this._timeoutInSeconds);
void installLogoutHandler() {
var body = document.getElementById(MAIN_CONTAINER_ID);
body.addEventListener("click", (event) => resetLogoutTimer());
body.addEventListener("keyup", (event) => resetLogoutTimer());
resetLogoutTimer();
}
/// Return the time left to logout in seconds.
/// If user is not authenticated then 0 is returned.
static int timeLeftInSeconds() {
if ((_timeLeft == null) || !Config.authStatus.authenticated) {
return 0;
}
return ((DateTime.now().millisecondsSinceEpoch - _timeLeft.millisecondsSinceEpoch) / 1000).floor();
}
void resetLogoutTimer() {
_timeLeft = DateTime.now();
_sessionTimer?.cancel();
_sessionTimer = Timer(Duration(seconds: _timeoutInSeconds), _logout);
}
void _logout() {
if (Config.authStatus.authenticated) {
ServiceLogin().logoutUser().then((result) {
Config.authStatus = AuthStatus();
_navigator.currentState.pushNamedAndRemoveUntil(
NavigationLinks.NAV_HOME, (Route<dynamic> route) => false);
});
}
}
}
Then use the SessionTimeoutHandler above in your main widget setup (see initState below).
class AppTaskTracker extends StatefulWidget {
#override
_AppTaskTrackerState createState() => _AppTaskTrackerState();
}
class _AppTaskTrackerState extends State<AppTaskTracker> {
final GlobalKey<NavigatorState> _navigator = GlobalKey<NavigatorState>();
#override
void initState() {
super.initState();
SessionTimeoutHandler(_navigator, Config.LOGOUT_TIMEOUT).installLogoutHandler();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
...
Take into account that SessionTimeoutHandler gets the navigator in order to redirect to home after automatic logout.