Thank you very much in advance for your help! Please let me know if the question is not clear i would be happy to add more details if needed.
I have a Finite State Machine that handles some audio recognition. This FSM is wrapped by a "manager" whose job is to handle the state transitions (processState, nextState). The FSM manager exposes a stream which is updated every time nextState is called
FSM/Manager layout
class FSM_Manager{
StreamController<RecognitionState> _stateStream =
StreamController<RecognitionState>();
Sink<RecognitionState> get _inState => _stateStream.sink;
Stream<RecognitionState> get outState => _stateStream.stream;
RecognitionState _currentState, _previousState;
void setState(RecognitionState state) {
_previousState = _currentState;
_currentState = state;
_addCurrentStateToStream();
}
void _addCurrentStateToStream() {
_inState.add(_currentState);
}
Future nextState() async {
_currentState.nextState(this);
}
Future processState(itemToRecognize) async {
await _currentState.processState(itemToRecognize);
}
}
abstract class BaseState {
RecognitionStateID get stateID; //enum with each state's ID
Future processState(itemToRecognize);
Future nextState(FSM_Manager manager);
}
class FSM_State1 implements BaseState{
bool isSuccess = false;
void processState(itemToRecognize) async {
isSuccess = await performRecognition(itemToRecognize);
}
void nextState (FSM_Manager fsmManager) {
if(isSuccess){
// go to next State
fsmManager.setState(NEXT_STATE);
} else {
//go to some other state
fsmManager.setState(SOME_OTHER_STATE);
}
}
}
class FSM_State2 implements BaseState{
bool isSuccess = false;
void processState(itemToRecognize) async {
isSuccess = await performRecognition(itemToRecognize);
}
void nextState () {
if(isSuccess){
// go to next State
} else {
// go to another State
}
}
}
I have a screen (Stateful Widget) which uses a StreamBuidler to listen to the "outState" stream in order to rebuild the screen with the information in the new State.
Stateful Widget
class _RecognitionScreenState extends State<RecognitionScreen> {
ItemToRecognize item;
var currStateiD;
FSM_Manager _fsmManager;
RecognitionScreenState(
ItemToRecognize item, FSM_Manager fsmManager) {
this.item = item;
this._fsmManager = fsmManager;
}
#override
Widget build(BuildContext context) {
String outString = '';
return StreamBuilder<RecognitionState>(
stream: _stateContext.outState,
builder: (BuildContext context,
AsyncSnapshot<RecognitionState> snapshot) {
if (snapshot.hasData) {
outString = snapshot.data.stateID.toString();
return Text(outString);
} else {
return Text('');
}
});
}
}
Now, I do not know where/how to call processState and nextState from, I cant do it from the build method so i Tried to use initState() and didUpdateWidget in the StatefulWidget so that the states are processed in the beginning and after every build respectively. This approach didnt work, the nextState method was never called. I feel like im missing something trivial but i just dont see where to call those functions from outside of the Stateful Widget in order to trigger a rebuild only after the state has changed
Thanks again for your help
EDIT
I apologize for the confusion,
I added the BaseState definition (just an abstract class with some method so that i dont forget to implement them)
the nextState method takes an FSM_Manager as a parameter and calls setState on success or failure and sets the next state
the States "implement" the BaseState class, they dont "extend" it
It's difficult for me to tell from your code because I don't know what your BaseState does but inside your builder - presumably from some event or callback - you would do:
inState.add(<--Some RecognitionState-->);
This would trigger the StreamBuilder to rebuild.
If everything else in your code is put together properly.
Related
I have a todo app built in Flutter and intended only for Android. I built a home screen widget for it (using the home_widget package in Flutter) to allow users to see a list of tasks and check them off directly from the widget.
At midnight, the tasks should reset with the new tasks for the day (I used the workmanager package to accomplish this, although I also tried the android_alarm_manager_plus package, with the same results). All of this functionality is working perfectly in debug mode, and even in profile mode (I can't test it in release mode because, according to my understanding, that would remove services and thus the home_widget would not work; however, when I do the build, that doesn't seem to be the problem because the home widget still shows up). BUT! When I build the release APK and submit it to Google Play for internal testing, then download it onto my Pixel 7 (with no power saving modes on, as far as I'm aware), the midnight function does not run. :(
Here's the relevant code:
main_prod.dart
void main() async {
return mainGeneric('Prod Name', ProdFirebaseOptions.currentPlatform, Environment.prod);
}
main_generic.dart
/// Used for Background Updates using Workmanager Plugin
#pragma('vm:entry-point')
void workmanagerCallbackDispatcher() {
Workmanager().executeTask((taskName, inputData) {
if (taskName == 'widgetBackgroundUpdate') {
try {
return Future.wait<void>([
// This is a static Future<void> function from a helper class that resets
// the tasks; it seems to be working when I test it by itself, as well as
// in debug or profile mode.
MidnightService.resetTasks(),
]).then((value) {
return Future.value(true);
});
} catch(err) {
print(err.toString());
throw Exception(err);
}
}
return Future.value(true);
});
}
void _startBackgroundUpdate() async {
if (await MidnightService.shouldUpdateWorkManagerTasks()) {
(await SharedPreferences.getInstance()).setInt('midnight_tasks_update_version', Constants.MIDNIGHT_TASKS_UPDATE_VERSION);
await Workmanager().cancelAll();
DateTime now = DateTime.now();
int nowMillis = now.millisecondsSinceEpoch;
int midnightTonightMillis = DateTime(now?.year ?? 0, now?.month ?? 0, (now?.day ?? 0) + 1).millisecondsSinceEpoch;
int millisUntilMidnight = midnightTonightMillis - nowMillis;
await Workmanager().registerPeriodicTask('midnightUpdate', 'widgetBackgroundUpdate', initialDelay: Duration(milliseconds: millisUntilMidnight), frequency: Duration(days: 1));
}
}
void mainGeneric(String appName, FirebaseOptions firebaseOptions, Environment environment) async {
// Avoid errors caused by flutter upgrade.
WidgetsFlutterBinding.ensureInitialized();
Workmanager().initialize(workmanagerCallbackDispatcher, isInDebugMode: kDebugMode).then((_) => _startBackgroundUpdate());
...
// If this is the first time opening the app with widget functionality.
HomeWidget.getWidgetData<String>('todays_tasks_string', defaultValue: '').then((todaysTasksString) async {
if (todaysTasksString == '') {
List<Task> todaysTasks = await Repositories().taskRepository.getFocusedTasks();
await HomeWidgetUtils.setTodaysTasks(todaysTasks);
return true;
}
return false;
});
Firebase.initializeApp(
name: appName,
options: firebaseOptions,
).then((_) async {
...
});
HomeWidget.registerBackgroundCallback(homeWidgetBackgroundCallback);
runApp(AppConfig(
child: MyApp(),
environment: environment,
appTitle: appName,
));
}
// Called when doing background work initiated from home screen widget
#pragma('vm:entry-point')
Future<void> homeWidgetBackgroundCallback(Uri uri) async {
if (uri.host.startsWith('completetask_')) {
String todaysTasksString = await HomeWidgetUtils.updateTaskById(uri.host.split('_')[1], true);
await HomeWidget.saveWidgetData<String>('todays_tasks_string', todaysTasksString);
await HomeWidget.updateWidget(androidName: 'TodaysTasksWidgetProvider');
}
}
midnight_service.dart
class MidnightService {
...
static Future<bool> shouldUpdateWorkManagerTasks() async {
try {
final prefs = await SharedPreferences.getInstance();
int midnightTasksUpdateVersion = prefs.getInt('midnight_tasks_update_version');
return Constants.MIDNIGHT_TASKS_UPDATE_VERSION > midnightTasksUpdateVersion;
}
catch (e) { print(e); }
return true;
}
}
It might also be valuable to note that, when a user checks off a task from the home screen widget, sometimes the task takes a while to actually be checked off (and sometimes requires the app to be opened before it will execute). However, I figured this is just a slowness issue or something controlled by the OS that I can't do much about.
With all of that, my question is then, why is the workmanager not executing its midnight task?
I've been smashing my head against this for days, so any help and/or advice is greatly appreciated!!
I am using christocracy's flutter_background_geolocation package to build a crowdsensing app. This app relies on the geofencing function of the aforementioned package quite heavily. In the main function, I have implemented a callback function that is as follows (partial code):
void _onGeofence(bg.GeofenceEvent event) async {
await showGeofenceNotification(flutterLocalNotificationsPlugin,
title: "Geofence", body: "$event", id: notification_id);
if (action == "ENTER") {
// update certain variables
BarometerService.startBarometerService();
BarometerService.floorChange.listen((floorChanges) {
// update floor
updateDatabase();
});
}
else if (action == "EXIT") {
// update certain variables
BarometerService.stopBarometerService();
}
updateDatabase();
setState(() {
// update UI
});
}
The code works perfectly when the app is open and in focus. However, when in background, the barometer service stops. The updateDatabase() function is also not carried out as my Firestore console doesn't get updated.
Here is the code for updating the database:
Future updateUserState(String matric, bool inLWN, bool inVaughan, String activity, int confidence, int floor) async {
return await userCollection.document(uid).setData({
'matric': matric,
'inLWN': inLWN,
'inVaughan': inVaughan,
'activity': activity,
'confidence': confidence,
'floor': floor,
});
}
And here is the code for BarometerService (which uses Flutter sensors plugin):
import 'package:sensors/sensors.dart';
static startBarometerService() {
Stream<BarometerEvent> barometer10Events = barometerEvents.throttle(Duration(seconds:PERIOD_SECONDS));
subscription = barometer10Events.listen(onBarometer);
streamController = new StreamController();
}
How do I make my services run even when app is closed or terminated? I have implemented the same code in my headless callback functions (except updating UI), but nothing besides updating my (local) variables and showing local flutter notifications is working.
Headless task for reference:
void headlessTask(bg.HeadlessEvent headlessEvent) async {
print('[BackgroundGeolocation HeadlessTask]: $headlessEvent');
switch(headlessEvent.name) {
case bg.Event.GEOFENCE:
bg.GeofenceEvent geofenceEvent = headlessEvent.event;
onHeadlessGeofence(geofenceEvent);
print('- [Headless] GeofenceEvent: $geofenceEvent');
break;
case bg.Event.ACTIVITYCHANGE:
bg.ActivityChangeEvent event = headlessEvent.event;
onHeadlessActivityChange(event);
print('- [Headless] ActivityChangeEvent: $event');
break;
}
}
onHeadlessGeofence is almost identical to the callback _onGeofence, besides the setState().
The full code can be found here
I am creating a list of businesses through RestAPI in initstate. I need to use sharedpreference value as one of its parameter but when i try to load data, the sharepreference value is null initial but that value can be used in other widgets easily.
The problem is how to read sharepreference value and use at the same time in initstate.?
I have tried many options like taking my api out of initstate and defining with async function and call it in initstate.
**#override
void initState() {
super.initState();
loadData();
getBizList();
}
getBizList() async {
await api
.bizListAPI(widget.subCatid, savedCityID, supSubFinal, areaFinal)
.then((response) {
setState(() {
bizListOBJ = response;
});
});
}
loadData() async {
SharedPreferences savedPref = await SharedPreferences.getInstance();
setState(() {
savedCity = (savedPref.getString('savedCity') ?? "Mumbai");
savedCityID = (savedPref.getString('savedCityID') ?? "1");
});
}**
in above code i am able to get or read data and show city name from sharedpreference(savedCity) which is in appbar but i want to use savedcityID data to pass in my api in initstate.
I also tried to use following plugin
[https://medium.com/#greg.perry/a-flutter-class-for-app-preferences-a256166ecc63.][1]
With this I was able to do what i wanted but each time i close my app and open again ...
i got following error
"_prefsInstance != null,
"Maybe call Prefs.getKeysF() instead. SharedPreferences not ready yet!");"
after that when i go back and open page again, the app and biz list works perfect till again i close my app.
[1]: https://medium.com/#greg.perry/a-flutter-class-for-app-preferences-a256166ecc63
sorry for my any noob explanation or question. Actually this is my 1st question in stack and I am beginner in flutter.
Can you try this?
void initState() {
super.initState();
loadData().then((_) {
getBizList();
});
}
You need to use set state inside init state because
loadData();
getBizList();
are async function so you need to refresh page to get value.
Try this
#override
void initState() {
// TODO: implement initState
super.initState();
setState(() {
loadData();
getBizList();
});
}
I'm currently working on a flutter app with a websocket.
I want to change the current screen with a Navigator.pushNamed(context, '/path') in the method that is triggered by the websocket on a new message. The problem is that I don't have any context in this method.
So how to change the current screen from this method ?
I dont know if I'm thinking in the wrong way or if I just don't understand something.
Here is the method that is triggered on a new websocket message, his class is not a widget. This object is contained in every screen I create but his variable _parent is always set to match the active screen.
onMessageReceived(data) {
print("new message: " + data + " !");
data = jsonDecode(data);
data.forEach((key, value) {
switch (key) {
case "state":
_parent.newState(value);
break;
}
});
}
Here is the method of the widget:
newState(state){
if(state == "start"){
Navigator.pushNamed(, "/path");
}
}
Thanks in advance
You can use a stream for that. You can register the websocket response, and when it returns you can add it to the stream, and then on your screen you use a StreamBuilder to subscribe to the stream and navigate when needed. Something like this:
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
// Create a stream to receive the values
var webSocketStream = new BehaviorSubject<String>();
onMessageReceived(data) {
print("new message: " + data + " !");
data = jsonDecode(data);
data.forEach((key, value) {
switch (key) {
case "state":
// Write to the stream
webSocketStream.add(value);
break;
}
});
}
class CheckWebSocket extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<String>(
// Subscribe to the stream
stream: webSocketStream,
builder: (context, snapshot) {
// When the stream changes navigate
if (snapshot.hasData && snapshot.data == "start") {
Navigator.pushNamed(context, "/path");
} else {
return Container();
}
}),
);
}
}
I used rxdart for that, because it makes dealing with streams simpler. I also recommend that you extract the code that reads the websocket to a separate class, I put everything in one file just to make it simpler to understand.
I am using MvvmCross 4.2.3 and I have a query about when it is safe to call ShowViewModel
I am trying to call ShowViewModel to navigate in the Start method of ViewModelA to navigate to ViewModelB, however I get the following exception
Java.Lang.IllegalStateException: Recursive entry to executePendingTransactions
I assumed I was doing it too early in the lifecycle of ViewA\ViewModelA. So I put the call into the OnResume of ViewA. I assumed at this point any transactions required to show ViewA would have been commited.
But I still get the same error.
Has anyone come accross this problem. If so how do I solve it.
Thanks in Advance
I am not being specific here , just trying to solve what you asked .
I think there could be two scenario .
scenerio first .
you want to navigate on user interaction lets say tapping on a button .
Inside view you can put this code in OnCreate or ViewModelSet method overrides .
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(MyButton).For(zz => zz.BindClick()).To(vm => vm.MyCommand);
Inside your viewmodel, you would need this .
private ICommand myCommand;
public virtual ICommand MyCommand
{
get
{
return myCommand = myCommand ?? new MvxCommand(() => {
Task.Factory.StartNew(() => {
ShowViewModel<MyNextViewModel>();
});
});
}
}
Scenario 2 ,
You have some Async task going on based on completion of that you want to navigate .
Inside your viewmodel constructor you call a method like below .
Public MyViewModel(){
LoadActivation()
}
private async void LoadActivation()
{
await Task.Run(async () =>
{
try {
response = await _Myservice.LoadMyData();
if(response != null ) {
ShowViewModel<MyNextViewModel>():
}
}
catch (Exception ex) {
Debug.WriteLine(ex);
}
});
}