Flutter onChange executed twice - android

I have a text field, which has the onChange property, when it detects that the text has a \ n execute a function, the problem is that this function is executed twice, it should be mentioned, that in that function, I clean the text of the TextController.
TextField(
maxLines: null,
controller: codigoController,
autofocus: true,
onChanged: (text) {
if (text.contains('\n')) {
test();
}
},
),
_test() {
print("hello");
codigoController.clear();
}

One of the solution is to use listener on TextController
1. Add Listener
we can add listener at the first time Screen is rendered. Later we need to dispose it as stated by docs
class _AutoCallApiState extends State<AutoCallApi> {
TextEditingController codigoController = TextEditingController();
#override
void initState() {
super.initState();
codigoController.addListener(changesOnField);
}
#override
void dispose() {
codigoController.dispose(); // release unused memory in RAM
super.dispose();
}
2. Handle changes and API Call
Future callApi(String textToSent) async {
await Future.delayed(Duration(seconds: 5));
print("Received OK from API");
codigoController.clear();
}
void changesOnField() {
print("Changes Called");
String text = codigoController.text;
if (text.isNotEmpty) {
print('$text');
if (text.contains('\n')) {
callApi(text);
}
}
}
3. Demo
callApi method only called once
Note : you may see at the demo, it only prints "Saved data to API" once
4. Full Repo
You may look into this repo and build it locally. Github

Maybe you can try this
SchedulerBinding.instance.addPostFrameCallback((it) => {_controller.clear()});

Related

Updating android wigdet with flutter background_fetch

I have an app based on flutter and created a Home screen widget for android (with home_widget) showing information from the app. With flutter background_fetch I update these information regularly, which works fine. Now when I restart my phone (emulator or real device), the background_fetch task does not continue, despite headless: true and stopOnTerminate: false set. Instead the old information from the latest fetch before the restart are displayed in the widget again.
main.dart
import 'package:home_widget/home_widget.dart';
import 'package:background_fetch/background_fetch.dart';
import 'package:logging_to_logcat/logging_to_logcat.dart';
import 'package:logging/logging.dart';
void main() {
runApp(
const MaterialApp(
home: MyApp()
)
);
BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}
// [Android-only] This "Headless Task" is run when the Android app is terminated with `enableHeadless: true`
// Be sure to annotate your callback function to avoid issues in release mode on Flutter >= 3.3.0
#pragma('vm:entry-point')
void backgroundFetchHeadlessTask(HeadlessTask task) async {
String taskId = task.taskId;
bool isTimeout = task.timeout;
if (isTimeout) {
// This task has exceeded its allowed running-time.
// You must stop what you're doing and immediately .finish(taskId)
debugPrint("[BackgroundFetch] Headless task timed-out: $taskId");
BackgroundFetch.finish(taskId);
return;
}
HomeWidget.saveWidgetData('refresh_date', "restarted");
HomeWidget.updateWidget(name: 'WidgetLarge', iOSName: 'WidgetLarge');
debugPrint('[BackgroundFetch] Headless event received.');
BackgroundFetch.finish(taskId);
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State createState() {
return MainPage();
}
}
class MainPage extends State {
#override
void initState() {
super.initState();
initPlatformState();
BackgroundFetch.start().then((int status) {
debugPrint('[BackgroundFetch] start success: $status');
}).catchError((e) {
debugPrint('[BackgroundFetch] start FAILURE: $e');
});
HomeWidget.saveWidgetData('refresh_date', "test2");
HomeWidget.updateWidget(name: 'WidgetLarge', iOSName: 'WidgetLarge');
}
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
// Configure BackgroundFetch.
int status = await BackgroundFetch.configure(BackgroundFetchConfig(
minimumFetchInterval: 15,
stopOnTerminate: false,
enableHeadless: true,
requiresBatteryNotLow: false,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
requiredNetworkType: NetworkType.ANY,
startOnBoot: true,
forceAlarmManager: true
), (String taskId) async { // <-- Event handler
// This is the fetch-event callback.
print("[BackgroundFetch] Event received $taskId");
setState(() {
latestUpdate = DateTime.now();
HomeWidget.saveWidgetData('refresh_date', "test");
HomeWidget.updateWidget(name: 'WidgetLarge', iOSName: 'WidgetLarge');
});
// IMPORTANT: You must signal completion of your task or the OS can punish your app
// for taking too long in the background.
BackgroundFetch.finish(taskId);
}, (String taskId) async { // <-- Task timeout handler.
// This task has exceeded its allowed running-time. You must stop what you're doing and immediately .finish(taskId)
debugPrint("[BackgroundFetch] TASK TIMEOUT taskId: $taskId");
BackgroundFetch.finish(taskId);
});
debugPrint('[BackgroundFetch] configure success: $status');
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
}
}
I import background_fetch like this:
dependencies:
...
home_widget: ^0.1.6
background_fetch:
git:
url: https://github.com/transistorsoft/flutter_background_fetch
I just updated flutter to the latest version with flutter upgrade and now it's working. Even tough the headless task begins executing 15 minutes after the reboot, so I still try to figure out how do execute it immediately after the reboot.

Flutter: Get the variable after the running of a function

I'm having a problem getting a variable after the function runs.
I have to read a qr code and then put the qr code in a text field, but when i try to read it gets the default variable text, and only gets the right qr code when i try to read for the seccond time.
Here is the qr reader function:
Future<void> scanQR() async {
String barcodeScanRes;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
barcodeScanRes = await FlutterBarcodeScanner.scanBarcode(
'#ff6666', 'Cancel', true, ScanMode.QR);
print(barcodeScanRes);
} on PlatformException {
barcodeScanRes = 'Failed to get platform version.';
}
if (!mounted) return;
setState(() {
_scanBarcode = barcodeScanRes;
});
}
Here the button to run the function and then put the qr on the controller:
onPressed: () {
scanQR();
setState(() {
_codprodController.text = _scanBarcode;
});
},
//on the first time i try it shows "unknown" that is the _scanBarCode default text instead of the qr i read
I guess the problem is the "_codprodController.text = _scanBarcode;" that its not runnig after (but together) the scanQR() but i dont know hot to fix.
Try below
onPressed: () async {
await scanQR();
setState(() {
_codprodController.text = _scanBarcode;
});
},

Update location in background?

I want to update user's location in background.
To perform tasks in background, i used this package, workmanager.
Link to that: https://pub.dev/packages/workmanager
But i can not update the location, it seems like it can't work with async code?
here is my code,
void callbackDispatcher() {
Workmanager.executeTask((task, inputData) async {
await _HomeScreenState().updateUserLoc();
print('it is working?');
return Future.value(true);
});
}
//Inside my stateFul Widget,
void initState() {
super.initState();
Workmanager.initialize(
callbackDispatcher,
isInDebugMode: true,
);
Workmanager.registerPeriodicTask(
"1",
fetchBackground,
frequency: Duration(minutes: 15),
);
}
updateUserLoc() async {
print("executi9ng the updateUserLocccccc");
await getusersLocation();
print("executi9ng the updateUserLoc");
GeoFirePoint point = geo.point(latitude: lat, longitude: long);
_firestore.collection('locations').document(widget.userid).setData(
{'position': point.data},
merge: true,
);
}
Is there any other way of updating the users's location in background?
You have to call the call the code inside the updateUserLoc() directly inside the callbackDispatcher. Just make the callbackDispatcher async function.

Flutter RawKeyboardListener listening twice?

What I am trying to achieve is when viewing this widget, the RawKeyboardListener starts listening straight away when the TextField is not selected/in focus. It runs the HandleKey function to deal with what I want to do with the keyCode.
The issue I am having is when running the app for the first time, the handleKey function seems to be running twice. So in the example below it would print why does this run twice $_keyCode TWICE when I only enter 1 key. I think it listens to keyUp AND keyDown. The result I want is for it to only run once...
However, the code works fine as well when I select the TextField and do a regular submit with the emulator keyboard.
I am struggling to understand why it only has a problem after interacting with the TextField. I feel like it needs a Future or await somewhere? but I have no idea.
Please help.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
class KeyboardListener extends StatefulWidget {
KeyboardListener();
#override
_RawKeyboardListenerState createState() => new _RawKeyboardListenerState();
}
class _RawKeyboardListenerState extends State<KeyboardListener> {
TextEditingController _controller = new TextEditingController();
FocusNode _textNode = new FocusNode();
#override
initState() {
super.initState();
}
//Handle when submitting
void _handleSubmitted(String finalinput) {
setState(() {
SystemChannels.textInput.invokeMethod('TextInput.hide'); //hide keyboard again
_controller.clear();
});
}
handleKey(RawKeyEventDataAndroid key) {
String _keyCode;
_keyCode = key.keyCode.toString(); //keycode of key event (66 is return)
print("why does this run twice $_keyCode");
}
_buildTextComposer() {
TextField _textField = new TextField(
controller: _controller,
onSubmitted: _handleSubmitted,
);
FocusScope.of(context).requestFocus(_textNode);
return new RawKeyboardListener(
focusNode: _textNode,
onKey: (key) => handleKey(key.data),
child: _textField
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Search Item")),
body: _buildTextComposer(),
);
}
}
Your callback is getting called for both keydown and keyup events with instances of following classes:
RawKeyDownEvent
RawKeyUpEvent
You can pass the whole object to handleKey, and filter based on runtime type of object. for example
handleKey(RawKeyEvent key) {
print("Event runtimeType is ${key.runtimeType}");
if(key.runtimeType.toString() == 'RawKeyDownEvent'){
RawKeyEventDataAndroid data = key.data as RawKeyEventDataAndroid;
String _keyCode;
_keyCode = data.keyCode.toString(); //keycode of key event (66 is return)
print("why does this run twice $_keyCode");
}
}
_buildTextComposer() {
TextField _textField = new TextField(
controller: _controller,
onSubmitted: _handleSubmitted,
);
FocusScope.of(context).requestFocus(_textNode);
return new RawKeyboardListener(
focusNode: _textNode,
onKey: handleKey,
child: _textField
);
}
If this still does not help, check actual runtimeTypes logged from handleKey method, and filter by those.
The onKey callback is triggered for both key down and key up events. That's why it appears to be called twice for a single key press.
When handling the events, I prefer using is rather than accessing the runtime type:
onKey: (RawKeyEvent event) {
if (event is RawKeyDownEvent) {
// handle key down
} else if (event is RawKeyUpEvent) {
// handle key up
}
},
You are right. RawKeyboardListener listens on raw keyboard events. Which means it returns down and up (or how the naming convention is on touchscreens). Knowing that you could simply create a if-statement and just get through the event once:
bool _tempKeyPressedOnce = false;
if (!_tempKeyPressedOnce) {
// do stuff
_tempKeyPressedOnce = true;
}
Eyo, played around with the values variables and noticed that if you use the iskeypressed
on the second time round it's false. Id hazard a guess that normally its either detecting the press and the release.
so
RawKeyboardListener(
focusNode: FocusNode(),
autofocus: true,
//includeSemantics: true,
onKey: (value){
print("1) ${value.data}");
print("2) ${value.character.toString()}");
print("3) ${value.toString()}");
print("4) ${value.physicalKey.debugName}");
print("5) ${value.logicalKey.keyId}");
print("6) ${value.isKeyPressed(LogicalKeyboardKey.enter)}");
setState(() {
///add string to list and clear text or not ?
value.logicalKey == LogicalKeyboardKey.enter ? print("YES A") : 0;
value.isKeyPressed(LogicalKeyboardKey.enter) ? print("YES B") : 0;
}
);
},
Results in a
flutter: 1) Instance of 'RawKeyEventDataWindows'
flutter: 2)
flutter: 3) RawKeyDownEvent#13d45(logicalKey: LogicalKeyboardKey#70028(keyId: "0x100070028", keyLabel: "Enter", debugName: "Enter"), physicalKey: PhysicalKeyboardKey#70028(usbHidUsage: "0x00070028", debugName: "Enter"))
flutter: 4) Enter
flutter: 5) 4295426088
flutter: 6) true
flutter: YES A
flutter: YES B
flutter: NEXT SET
flutter: ***********************************
flutter: 1) Instance of 'RawKeyEventDataWindows'
flutter: 2) null
flutter: 3) RawKeyUpEvent#9dc07(logicalKey: LogicalKeyboardKey#70028(keyId: "0x100070028", keyLabel: "Enter", debugName: "Enter"), physicalKey: PhysicalKeyboardKey#70028(usbHidUsage: "0x00070028", debugName: "Enter"))
flutter: 4) Enter
flutter: 5) 4295426088
flutter: 6) false
flutter: YES A
flutter: NEXT SET
flutter: ***********************************
Using isKeyPressed worked for me.
My Working code
RawKeyboardListener(
focusNode: _focusNodeKeyboard,
onKey: (event) {
if (event.isKeyPressed(LogicalKeyboardKey.backspace)) {
print('Backspace Pressed'); // Printed Once
}
},
)
Older Version
RawKeyboardListener(
focusNode: _focusNodeKeyboard,
onKey: (event) {
if (event.logicalKey == LogicalKeyboardKey.backspace) {
print('Backspace Pressed'); // Printed Twice
}
},
)
This is how you can get it to work:
RawKeyboardListener(
focusNode: FocusNode(),
onKey: (event) {
// Only taking key down event into consideration
if (event.runtimeType == RawKeyDownEvent) {
bool shiftPressed = event.isShiftPressed; // true: if shift key is pressed
}
},
child: TextField(),
)
I use this function to write values from the keyboard
handleKey(RawKeyEvent event) {
if (event is RawKeyDownEvent) {
if (event.physicalKey == PhysicalKeyboardKey.enter) {
log('ENTER');
_text = '';
} else {
log('Event data keyLabel ${event.data.keyLabel}');
_text += event.data.keyLabel;
}
log('text: $_text');
}
}
It does not duplicate writing values from the keyboard

Xamarin - Swipe to refresh the refresh icon still there after execute command

I am using xamarin forms to develop my project, and now I am using the Jason Smith Components to refresh my view.
Xamarin.Forms-PullToRefreshLayout
I am using the following code:
scrollControl.RefreshCommand = RefreshCommand;
public ICommand RefreshCommand
{
get {return new Command(async () => await ContentScrollView_Scrolled()); }
}
async Task ContentScrollView_Scrolled()
{
......
}
After I execute the code, the refresh icon still keep spinning. I tried to put the scrollControl.isRefreshing = false at the end. It does not work.
I suspect that when your refresh completes that you are not updating the IsRefreshing property on the UI thread. Try this:
// after your refresh command completes
Device.BeginInvokeOnMainThread (() => {
scrollControl.IsRefreshing = false;
});
Ok well, this solution is kinda weird, and i found it, since the isrefreshing property is false, so i set the isrefreshing to true at the beginning of code, i thought it will auto set to true so i have to manual it myself, in the end it is work like charm
scrollControl.RefreshCommand = RefreshCommand;
public ICommand RefreshCommand
{
get {return new Command(async () => await ContentScrollView_Scrolled()); }
}
async Task ContentScrollView_Scrolled()
{
scrollControl.isRefreshing = true;
......
scrollControl.isRefreshing = false;
}

Categories

Resources