How to call MethodChannel from an isolate created by android_alarm_manager? - android

I tried to schedule background service. For that i used https://pub.dev/packages/android_alarm_manager. It works well.
For my example, I tried to get from my isolate (android_alarm_manager's callback) the battery level following the flutter tutorial : https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-java-tab.
If I call manualy my callback it works (so I well do the Android part). If android_alarm_manager call it, I got the following error appear :
Unhandled Exception: MissingPluginException(No implementation found for method getBatteryLevel on channel net.example.com/battery)
It's weird because, from an other isolate where I used https://pub.dev/packages/flutter_downloader to download file, this plugin used MethodChannel...
Here is my code for android_alarm_manager :
import 'package:flutter/services.dart';
class AndroidManagerCallBack {
static Future<void> main() async {
_AndroidManagerCallBack test = _AndroidManagerCallBack();
await test.getBatteryLevel();
}
}
class _AndroidManagerCallBack {
static const platform = const MethodChannel('net.example.com/battery');
Future<void> getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
print(batteryLevel);
}
}
I simply call the callback like :
AndroidAlarmManager.periodic(
Duration(seconds: 20),
0,
AndroidManagerCallBack.main(),
rescheduleOnReboot: true,
wakeup: false,
);
In android_alarm_manager's callback, I can call plugins which used somee MethodChannel but when I tried with my MethodChannel, I got errors...
Someone can guide me :) ?

It seem impossible to call directly MethodChannel through an isolate.
But, with the creation of a plugin i can achieve what i want. So the solution is to create a plugin :) !

Related

Android Studio Flutter Android App issues with Work Manager and Sqflite Working together

I am having an issue when work managers scheduler runs it gets stuck on reading data from database:
Future<List<TimesheetDays>> getTimesheetDays() async{
print('Getting DB');
Database db = await instance.database;
print('Getting DB stuff');
var timesheetday = await db.query('TimesheetDays',orderBy: 'TimesheetDayId');
// print(timesheetday);
List<TimesheetDays> timesheetDaysList = timesheetday.isNotEmpty ?
timesheetday.map((e) => TimesheetDays.fromMap(e)).toList() : [];
return timesheetDaysList;
}
It gets stuck on the
await instance.database
part of the code, now strange thing is that I have this project set on PC and Laptop. It only fails to work on PC whereas its fine on Laptop.
Work Manager code in case:
Callback Dispatcher
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
print('Executed scheduler!!');
try {
print('entered try catch!');
await CustomHttpRequests().synchronizeTimesheetDataLocally();
}
catch(_){
print('Error during execution');
}
print('Completed synchronization');
return Future.value(true);
});
}
Rest of the code:
Future<void> main() async{
WidgetsFlutterBinding.ensureInitialized();
print('creating task manager');
await Workmanager().initialize(
callbackDispatcher, // The top level function, aka callbackDispatcher
isInDebugMode: true, // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
);
Workmanager().registerPeriodicTask("1", fetchBackground, frequency: Duration(minutes: 15),initialDelay: Duration(minutes: 2)); //Android only (see below)
print('Task manager created');
runApp(
MultiProvider(providers: [
ChangeNotifierProvider(create: (_) => CoreUser())
],
child: MyApp(),)
);
}
And just so the call in dispatcher makes sense it references straight to database in its first line so this is where it fails:
Future synchronizeTimesheetDataLocally() async{
//await synchronizeWasteGradings();
print("started reading");
var timesheetData = await DatabaseHelper.instance.getTimesheetDays();
Some things I tried to do to fix it:
"flutter clean"
Made sure MainActivity and GeneratedPluginRegistrant are the same on both devices
(sqflite & work manager are both being registered properly)
Basically went through this:
https://github.com/tekartik/sqflite/blob/master/sqflite/doc/troubleshooting.md
Any ideas how to fix this?

Flutter Firebase Functions: An error occured while calling function

I recently started developing an app using Flutter and Firebase. I use Firebase Emulator to test Authentication and Cloud Functions. Most of my code is in the Firebase Cloud Functions which I use for all CRUD for Firestore and RTDB. While adding some new features, I got this error in my app. I tried searching a lot but could not find any solution. The following is the error is receive:
An error occured while calling function profile-get
Error Details: null
Message: An internal error has occurred, print and inspect the error details for more information.
Plugin: firebase_functions
Stacktrace: null
My API class in Flutter:
class Api {
Api(this.functions);
final FirebaseFunctions functions;
static Api init() {
FirebaseFunctions functions = FirebaseFunctions.instance;
if (emulator) functions.useFunctionsEmulator(origin: host);
return Api(functions);
}
Future<ApiResult> call(String name, {
Map<String, dynamic> parameters,
}) async {
try {
HttpsCallable callable = functions.httpsCallable(name);
HttpsCallableResult results = await callable.call(parameters);
return ApiResult(new Map<String, dynamic>.from(results.data));
} on FirebaseFunctionsException catch (e) {
print('An error occurred while calling function $name.');
print('Error Details: ${e.details}\nMessage: ${e.message}\nPlugin: ${e.plugin}\nStacktrace: ${e.stackTrace}');
return ApiResult({
'status': 'error',
'message': 'An error occured',
'code': 'unknown'
});
}
}
static String get host => Platform.isAndroid ? 'http://10.0.2.2:2021' : 'http://localhost:2021';
}
I tried running the functions directly from their local URL and they work fine.
As mentioned in the comments defore you are reating a cloud function with onRequest. Those are not callable using an SDK but only trough https URL.
To create a callable function that you can call trough Firebase SDKs you would need to refactor your functions to use the onCall.
It should look something like this:
exports.yourFunctionName= functions.https.onCall((data, context) => {
// receive the data
const text = data.text;
// return a response
return {
test:'test'
}
});
Here you have more information how the callable functions work.
Are you using a different region than the standard us-central1? This is often the case, so you need to change the region you are calling from
HttpsCallable callable = FirebaseFunctions.instanceFor(region:"your_region").httpsCallable(name);

React Native : Native method ExpoLocalAuthentication.authenticateAsync expects 0 arguments but received 1

I'm in need of your expertise in React Native.
I'm trying to use expo-local-authentication for local fingerprint authentication for my application.
My project was created using expo init command.
I have done the setup as per the documentation and still running into a strange issue:
Below is the error I'm facing for LocalAuthentication.authenticateAsync(options):
Native method ExpoLocalAuthentication.authenticateAsync expects 0
arguments but received 1
Here is the required part of my code:
import * as LocalAuthentication from 'expo-local-authentication';
const authenticate = async () => {
const hasHardwareAsync = await LocalAuthentication.hasHardwareAsync();
if (hasHardwareAsync) {
const supportedAuthentications = await LocalAuthentication.supportedAuthenticationTypesAsync();
if (supportedAuthentications.indexOf(1) !== -1) {
// Finger print supported
const isFingerprintEnrolled = await LocalAuthentication.isEnrolledAsync();
if (isFingerprintEnrolled) {
const options = {
promptMessage: 'Authenticate yourself',
};
try {
// Also tried with await but it throws the same error
// await LocalAuthentication.authenticateAsync(options)
LocalAuthentication.authenticateAsync(options).then(result => {
// I never get inside this block
console.warn(result)
})
.catch(error => {
console.warn('Authentication Error: ', error)
})
} catch (error) {
console.warn(error)
}
}
}
}
}
Not sure what I'm missing. Seems like there is no information available about the error. I also tried to run the LocalAuthentication.authenticateAsync() without any arguments but it still throws the same error.
Any help on what could be the root cause of the issue and how can I resolve it or any other alternative for local authentication would be highly appreciated.
Update your app to the latest version of expo (38 in my case) and to the latest version of expo-local-authentication, and the error goes away.

errors: expo-camera.isAvailableAsync and expo-camera.getAvailableCameraTypesAsync is not available on android

expo-camera: "^8.0.0"
sdkVersion: "36.0.0"
Hello people, when i try:
import { Camera } from 'expo-camera';
...
const cameraIsAvailable = await Camera.isAvailableAsync()
const availablesCameraTypes = await Camera.getAvailableCameraTypesAsync()
console.log("cameraIsAvailable: ", cameraIsAvailable)
console.log("availablesCameraTypes: ", availablesCameraTypes)
i get the fallowing errors:
expo-camera.isAvailableAsync is not available on android, are you sure you've linked all the native dependencies properly?
The method or property expo-camera.getAvailableCameraTypesAsync is not available on android, are you sure you've linked all the native dependencies properly?
the problem just disappear when i remove:
state = {
...
cameraType: Camera.Constants.Type.front,
};
...
<Camera
type={this.state.cameraType}
flashMode={flashMode}
style={styles.preview}
ref={camera => this.camera = camera}
/>
and change it by:
state = {
...
cameraType: Camera.Constants.Type.back,
};
and i change "cameraType" by
componentDidMount = () => {
this.props.navigation.addListener('didFocus', async () => {
await setTimeout(() => {
this.setState({ cameraType: Camera.Constants.Type.front })
}, 100)
});
}
it seems its an error from expo-camera...
so when i try to call these methods:
const cameraIsAvailable = await Camera.isAvailableAsync()
const availablesCameraTypes = await Camera.getAvailableCameraTypesAsync()
i get following errors: errors: expo-camera.isAvailableAsync and expo-camera.getAvailableCameraTypesAsync is not available on android
The methods you're trying to use, Camera.isAvailableAsync and Camera.getAvailableCameraTypesAsync are marked in the documentation as Web only, so calling them will only work, well, on Web.
In code run in react-native context (as opposed to browser context) just check permissions and you should be good to go!

How can I mock/stub out a Flutter platform channel/plugin?

I read the introduction to platform-specific plugins/channels on the Flutter website and I browsed some simple examples of a plugin, like url_launcher:
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/services.dart';
const _channel = const MethodChannel('plugins.flutter.io/url_launcher');
/// Parses the specified URL string and delegates handling of it to the
/// underlying platform.
///
/// The returned future completes with a [PlatformException] on invalid URLs and
/// schemes which cannot be handled, that is when [canLaunch] would complete
/// with false.
Future<Null> launch(String urlString) {
return _channel.invokeMethod(
'launch',
urlString,
);
}
In widgets tests or integration tests, how can I mock out or stub channels so I don't have to rely on the real device (running Android or iOS) say, actually launching a URL?
MethodChannel#setMockMethodCallHandler is deprecated and removed as of now.
Looks like this is the way to go now:
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void mockUrlLauncher() {
const channel = MethodChannel('plugins.flutter.io/url_launcher');
handler(MethodCall methodCall) async {
if (methodCall.method == 'yourMethod') {
return 42;
}
return null;
}
TestWidgetsFlutterBinding.ensureInitialized();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, handler);
}
The details are on GitHub.
And here is a tested example for package_info plugin for future references:
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void mockPackageInfo() {
const channel = MethodChannel('plugins.flutter.io/package_info');
handler(MethodCall methodCall) async {
if (methodCall.method == 'getAll') {
return <String, dynamic>{
'appName': 'myapp',
'packageName': 'com.mycompany.myapp',
'version': '0.0.1',
'buildNumber': '1'
};
}
return null;
}
TestWidgetsFlutterBinding.ensureInitialized();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, handler);
}
You can use setMockMethodCallHandler to register a mock handler for the underlying method channel:
https://docs.flutter.io/flutter/services/MethodChannel/setMockMethodCallHandler.html
final List<MethodCall> log = <MethodCall>[];
MethodChannel channel = const MethodChannel('plugins.flutter.io/url_launcher');
// Register the mock handler.
channel.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
await launch("http://example.com/");
expect(log, equals(<MethodCall>[new MethodCall('launch', "http://example.com/")]));
// Unregister the mock handler.
channel.setMockMethodCallHandler(null);
When you create a plugin, you are automatically provided a default test:
void main() {
const MethodChannel channel = MethodChannel('my_plugin');
setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return '42';
});
});
tearDown(() {
channel.setMockMethodCallHandler(null);
});
test('getPlatformVersion', () async {
expect(await MyPlugin.platformVersion, '42');
});
}
Let me add some notes about it:
Calling setMockMethodCallHandler allows you to bypass whatever the actual plugin does and return your own value.
You can differentiate methods using methodCall.method, which is a string of the called method name.
For plugin creators this is a way to verify the public API names, but it does not test the functionality of the API. You need to use integration tests for that.

Categories

Resources