I have a method channel registered in the Android build's MainActivity class, which works fine when called from the foreground application.
I want to call this method channel from a task that Workmanager runs in the background, but I'm getting a MissingPluginException every time.
I'm guessing this doesn't work because a background task doesn't initialise the MainActivity, but I can't seem to find any information on how to register the channels in a place where the workmanager can call them.
My (simplified) setup is as follows:
lib/main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
Workmanager().registerPeriodicTask('uniquename', 'taskName',
frequency: const Duration(minutes: 15),
constraints: Constraints(networkType: NetworkType.connected));
runApp(const MyApp());
}
void callbackDispatcher() {
Workmanager().executeTask((taskName, inputData) async {
bool result = await DeviceInfoModel().getDeviceInfo();
return Future.value(result);
});
}
lib/device_info.dart:
class DeviceInfoModel extends ChangeNotifier {
static const platform = MethodChannel('deviceInfo');
Future<void> getDeviceInfo() async {
final int result = await platform.invokeMethod('getBatteryLevel');
// And other similar calls to other methods
}
}
android/app/src/main/kotlin/nl/myapp/MainActivity.kt:
package nl.myapp
class MainActivity : FlutterActivity() {
private val DEVICE_INFO_CHANNEL = "deviceInfo"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, DEVICE_INFO_CHANNEL)
methodChannel?.setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
result.success(100)
}
}
}
}
Related
1) From loading_screen.dart :
Error : Instance member 'getData' can't be accessed using static access.
Locator( ) is my location handling class.
import 'package:flutter/material.dart';
import 'package:clima/services/location_handler.dart';
import 'package:clima/services/networking.dart';
const myAPI = 'cant post api online but its just alphabets and numbers passed as a string';
class LoadingScreen extends StatefulWidget {
#override
_LoadingScreenState createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
late double latitood;
late double longitood;
void initState() {
//this method is called the moment we run our app.
super.initState();
getLocationData();
}
void getLocationData() async {
Locator loca = Locator();
await loca.getCurrentLocation();
latitood = loca.latitude;
longitood = loca.longitude;
NetworkHelper NetHelp = NetworkHelper(
//pasing url info into Network Helper class.
'https://api.openweathermap.org/data/2.5.weather?lat=$latitood&lon=$longitood&appid=$myAPI');
var weatherDataFinal = await NetworkHelper.getData();
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
2) From networking.dart :
Error : The argument type 'String' can't be assigned to the parameter type 'Uri'.
import 'dart:convert'; /
import 'package:http/http.dart' as http;
class NetworkHelper {
NetworkHelper(this.url);
final String url;
Future getData() async {
http.Response response = await http.get(url);
//passing url as a single string to get method to get Response.
if (response.statusCode == 200) {
String data = response.body;
var decodedData = jsonDecode(data);
//decoding and putting data into decodedData variable of dynamic type.
return decodedData;
} else {
print(response.statusCode);
}
}
}
Did someone encounter these problems ? If you did and found a Solution then please help me !!!!
This await NetworkHelper.getData(); should be await NetHelp.getData(); because that is how you named your variable.
await http.get(url); should be await http.get(Uri.parse(url));
I'm using awesome_notifications and firebase_messaging for notifications.
AwesomeNotifications().actionStream.listen to set a action when the notification is tapped.
Currently when the app is Foreground or Background i can get the payload and open the spectific screen.
My problem is when the app are terminated, i dont know how to get the payload and navigate to some screen.
Here is my code
AwesomeNotifications().actionStream.listen(
(
ReceivedNotification receivedNotification,
) {
final navigation = locator<NavigationService>();
if (receivedNotification.payload != null) {
String? aux = receivedNotification.payload!['args'];
String route = receivedNotification.payload!['route']!;
if (aux == null) {
navigation.navigateTo(route);
} else {
final Map<String, dynamic> args = jsonDecode(aux);
dynamic pageArgs = route == RoutesNames.chatPage
? ChatConversationInfo.fromMap(args)
: AnnouncDetailPageArgs.fromMap(args);
navigation.navigateTo(
route,
arguments: pageArgs,
);
}
}
});
class FirebaseMessagingService {
FirebaseMessagingService();
Future<void> initialize() async {
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
badge: true,
sound: true,
alert: true,
);
_onMessage();
}
// Foreground
_onMessage() {
FirebaseMessaging.onMessage.listen((message) {
print('----- _onMessage');
AwesomeNotifications().createNotificationFromJsonData(message.data);
});
}
Future<String?> getDeviceFirebaseToken() async {
return await FirebaseMessaging.instance.getToken();
}
}
Future<void> _backgroundHandler(RemoteMessage message) async {
print('_backgroundHandler: ${message.data}');
AwesomeNotifications().createNotificationFromJsonData(message.data);
}
void main() async {
setupLocator();
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(_backgroundHandler);
await locator<AwesomeNotificationsServices>().initialize();
await locator<FirebaseMessagingService>().initialize();
runApp(const MyApp());
}
i followed the steps given here
and after adding this
import 'package:mixpanel_flutter/mixpanel_flutter.dart';
...
class _YourClassState extends State<YourClass> {
Mixpanel mixpanel;
#override
void initState() {
super.initState();
initMixpanel();
}
Future<void> initMixpanel() async {
mixpanel = await Mixpanel.init("Your Mixpanel Token", optOutTrackingDefault: false);
}
...
the terminal is giving me an error saying
Field 'mixpanel' should be initialized because its type 'Mixpanel' doesn't allow null.
Mixpannel mixpannel;
failed to compile application
i have inserted the mixpannel token.
in Mixpannel, i started a test project added an event chart for all events.
Have you checked the example which is given by mixpanel-flutter
You can create the instance for it:
class MixpanelManager {
static Mixpanel? _instance;
static Future<Mixpanel> init() async {
if (_instance == null) {
_instance = await Mixpanel.init("YOUR_PROJECT_TOKEN",
optOutTrackingDefault: false);
}
return _instance!;
}
}
And you can use likewise (in other class):
late final Mixpanel _mixpanel;
#override
initState() {
super.initState();
_initMixpanel();
}
Future<void> _initMixpanel() async {
_mixpanel = await MixpanelManager.init();
}
Check out the example for more.
how i must write this code in bloc v.8 i don know how i see some searches but im not understand and this is my code for classes they give me error => StateError (Bad state: add(DoFetchEvent) was called without a registered event handler.
Make sure to register a handler via on((event, emit) {...})):
class PostBloc extends Bloc<PostEvent, PostState> {
PostRepository repo;
PostBloc(PostState initialState, this.repo) : super(initialState);
Stream<PostState> mapEventToState(PostEvent event) async* {
if (event is DoFetchEvent) {
yield LoadingState();
try {
var posts = await repo.fetchPosts();
yield FetchSuccess(posts: posts);
} catch (e) {
yield ErrorState(message: e.toString());
}
}
}
}
import 'package:equatable/equatable.dart';
class PostEvent extends Equatable {
#override
List<Object?> get props => [];
}
class DoFetchEvent extends PostEvent {}
class PostState extends Equatable {
#override
List<Object?> get props => [];
}
class InitialState extends PostState {}
class LoadingState extends PostState {}
class FetchSuccess extends PostState {
List<PostModel> posts;
FetchSuccess({required this.posts});
}
class ErrorState extends PostState {
String message;
ErrorState({required this.message});
}
void main() {
runApp(MaterialApp(
home: BlocProvider(
create: (context) => PostBloc(InitialState(), PostRepository()),
child: MyApp(),
),
));
}
You can set your InitialState directly in the super constructor without manually passing it in like so.
PostBloc(this.repo) : super(InitialState()) {
on<DoFetchEvent>(_onDoFetchEvent);
}
Then you no longer pass in any state in the BlocProvider
BlocProvider<PostBloc>(
create: (BuildContext context) => PostBloc(PostRepository()),
...
Then your mapEventToState gets replaced with a method that takes the relevant event, and an Emitter<PostState> as arguments. yield then gets replaced with emit in the method.
Your whole class would look like this.
PostBloc(this.repo) : super(InitialState()) {
on<DoFetchEvent>(_onDoFetchEvent);
}
_onDoFetchEvent(
DoFetchEvent event,
Emitter<PostState> emit,
) async {
emit(LoadingState());
try {
var posts = await repo.fetchPosts();
emit(FetchSuccess(posts: posts));
} catch (e) {
emit(ErrorState(message: e.toString()));
}
}
}
That should do it.
Besides that, you're probably getting linter warnings about must_be_immutable on your state classes because PostState extend Equatable.
So I suggest making all PostState parameters final and adding the props override from Equatable to your state classes.
class ErrorState extends PostState {
final String message;
ErrorState({required this.message});
#override
List<Object?> get props => [message];
}
Using Flutter, a kotlin/swift function can be called by something like:
file.dart:
static const platform = const MethodChannel('my.test.flutterapp/battery');
final int result = await platform.invokeMethod('getBatteryLevel');
file.kt:
private val CHANNEL = "my.test.flutterapp/battery"
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
...
} else {
result.notImplemented()
}
}
Is there something similar to call Kotlin function from standard Dart app, like Dart console app!
https://flutter.io/developing-packages/
Plugin packages: A specialized Dart package which contain an API written in Dart code combined with a platform-specific implementation for Android (using Java or Kotlin), and/or for iOS (using ObjC or Swift). A concrete example is the battery plugin package.
...
flutter create --template=plugin -i swift -a kotlin hello
For the VM the mechanisms available are basic OS operations and native extensions.
By OS operations I mean, you could launch a separate process and interact with it, using files or the network stack. This is probably not as fine grained as you're looking for.
Native extensions allow you to call out to C or C++ code. I don't know enough about kotlin to know if you can easily expose functionality to C/C++. If it's possible, this will give you the tightest integration.
https://www.dartlang.org/articles/dart-vm/native-extensions
You can see this project: Full Sample
In Andriod:
class MainActivity: FlutterActivity() {
private val DATA_CHANNEL = "app.channel.shared.data"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), DATA_CHANNEL).setMethodCallHandler { call, result ->
if (call.method!!.contentEquals("getSharedText")) {
result.success("Shared Text")
}
}
}
}
In Dart:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
static const platform = const MethodChannel('app.channel.shared.data');
String dataShared = "No Data";
#override
void initState() {
super.initState();
getSharedText();
}
getSharedText() async {
var sharedData = await platform.invokeMethod("getSharedText");
if (sharedData != null) {
setState(() {
dataShared = sharedData;
});
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home:Scaffold(body: Center(child: Text(dataShared)))
);
}
}