I get an IllegalStateException sometimes in flutter app after adding native ads to a listview - flutter

this is the error I get:
setState() called after dispose(): _PlatformViewLinkState#bc2a5(lifecycle state: defunct, not mounted)
This error happens if you...
E/flutter ( 4758): [ERROR:flutter/shell/platform/android/platform_view_android_jni_impl.cc(49)] java.lang.IllegalStateException: PlatformView#getView() returned null, but an Android view reference was expected.
I'm using the google_mobile_ads package and not the firebase_ads one since it's deprecated.
I'm trying to put native ads in a list of cards, after a fixed number of elements in the listview.
The way I did this is by using a provider in the main.dart file:
...
void main() {
WidgetsFlutterBinding.ensureInitialized();
final initFuture = MobileAds.instance.initialize();
final adState = AdState(initFuture);
runApp(Provider.value(
value: adState,
builder: (context, child) => MyApp(),
));
}
...
the screen containing the listview is called chapters_screen.dart, when going to this screen the app sometimes crashes. In this file there is a list of items List<Object> itemList = []; as a state variable. In init state the list is populated with offline data as so: itemList = List.from(chaptersData);,
this is how I add the ads in the list:
#override
void didChangeDependencies() {
super.didChangeDependencies();
final adState = Provider.of<AdState>(context);
if(this.mounted){
adState.initialization.then((status) {
if(this.mounted){
setState(() {
for (int i = itemList.length - 2; i >= 1; i -= 2) {
itemList.insert(
i,
// BannerAd(
// adUnitId: adState.bannerAdUnitId,
// size: AdSize.banner,
// request: AdRequest(),
// listener: adState.adListener,
// )..load(),
NativeAd(
adUnitId: adState.nativeAdUnitId,
factoryId: 'listTile',
request: AdRequest(),
listener: NativeAdListener(
onAdLoaded: (_) {
print('ad is loaded succesfully!');
},
onAdFailedToLoad: (ad, error) {
// Releases an ad resource when it fails to load
ad.dispose();
print('Ad load failed (code=${error.code} message=${error.message})'); },
),
)..load(),
);
}
});
}
});
}
}
I tried it before with a banner ad and there is no problem. I followed this tutorial: https://www.youtube.com/watch?v=m0d_pbgeeG8 but I had to change the google_mobile_ads package version to a newer one to be able to use the same NativeAdFactory as in the flutter documentation. The problem is in the newer version the AdListener object no longer exists so instead of passing: listener: adState.adListener like in the video. I put the listener as the code above in the 'NativeAd()' widget.
this is how I'm checking for the type of the listItem to build it in the listView:
Widget _buildListView(BuildContext context) {
List<Widget> widgetList = [];
for (int i = 0; i < itemList.length; i++) {
if (itemList[i] is NativeAd) {
widgetList.add(Container(
height: 200,
child: AdWidget(ad: itemList[i] as NativeAd),
color: Colors.black));
} else {
widgetList.add(ChapterCard(chapter: Chapter.fromJson(itemList[i])));
}
}
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/blured_background.jpg"),
fit: BoxFit.cover,
),
),
child: ListView(children: widgetList.toList()),
);
}
I think I'm doing something wrong in the provider or in the factory class but I'm not sure.
the factory class for android (in java):
import com.google.android.gms.ads.nativead.NativeAd;
import com.google.android.gms.ads.nativead.NativeAdView;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.Map;
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin;
class ListTileNativeAdFactory implements GoogleMobileAdsPlugin.NativeAdFactory {
private final Context context;
ListTileNativeAdFactory(Context context) {
this.context = context;
}
#Override
public NativeAdView createNativeAd(
NativeAd nativeAd, Map<String, Object> customOptions) {
NativeAdView nativeAdView = (NativeAdView) LayoutInflater.from(context)
.inflate(R.layout.list_tile_native_ad, null);
TextView attributionViewSmall = nativeAdView
.findViewById(R.id.tv_list_tile_native_ad_attribution_small);
TextView attributionViewLarge = nativeAdView
.findViewById(R.id.tv_list_tile_native_ad_attribution_large);
ImageView iconView = nativeAdView.findViewById(R.id.iv_list_tile_native_ad_icon);
NativeAd.Image icon = nativeAd.getIcon();
if (icon != null) {
attributionViewSmall.setVisibility(View.VISIBLE);
attributionViewLarge.setVisibility(View.INVISIBLE);
iconView.setImageDrawable(icon.getDrawable());
} else {
attributionViewSmall.setVisibility(View.INVISIBLE);
attributionViewLarge.setVisibility(View.VISIBLE);
}
nativeAdView.setIconView(iconView);
TextView headlineView = nativeAdView.findViewById(R.id.tv_list_tile_native_ad_headline);
headlineView.setText(nativeAd.getHeadline());
nativeAdView.setHeadlineView(headlineView);
TextView bodyView = nativeAdView.findViewById(R.id.tv_list_tile_native_ad_body);
bodyView.setText(nativeAd.getBody());
bodyView.setVisibility(nativeAd.getBody() != null ? View.VISIBLE : View.INVISIBLE);
nativeAdView.setBodyView(bodyView);
nativeAdView.setNativeAd(nativeAd);
return nativeAdView;
}
}
The MainActivity class:
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin;
public class MainActivity extends FlutterActivity {
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
// TODO: Register the ListTileNativeAdFactory
GoogleMobileAdsPlugin.registerNativeAdFactory(flutterEngine, "listTile",
new ListTileNativeAdFactory(getContext()));
}
#Override
public void cleanUpFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.cleanUpFlutterEngine(flutterEngine);
// TODO: Unregister the ListTileNativeAdFactory
GoogleMobileAdsPlugin.unregisterNativeAdFactory(flutterEngine, "listTile");
}
}

Related

Why Flutter hybrid app does not start? I have used webview and google mobad that should show an ad when the app started

I am about to create an app using webview in flutter language and I want to show open app in mobad when the app opened or started.
The code you provided is written in Dart programming language. It initializes an instance of the "AppOpenAdManager" class and calls the "loadAd" method on it. Then, it initializes an instance of the "AppLifecycleReactor" class and passes the "appOpenAdManager" instance to its constructor.
The purpose of this code is to load and manage an advertisement using the "AppOpenAdManager" class, and to monitor the lifecycle events of the app using the "AppLifecycleReactor" class.
I am very new to flutter, thank you for helping me.
here's main.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:factorial/app_open_ad_manager.dart';
import 'app_lifecycle_reactor.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
MobileAds.instance.initialize();
late AppLifecycleReactor _appLifecycleReactor;
AppOpenAdManager appOpenAdManager = AppOpenAdManager()..loadAd();
_appLifecycleReactor =
AppLifecycleReactor(appOpenAdManager: appOpenAdManager);
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
#override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> with WidgetsBindingObserver {
late final WebViewController controller;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(
Uri.parse('https://example.com'),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: WebViewWidget(
controller: controller,
),
);
}
}
here's app_open_ad_manager.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'dart:io' show Platform;
class AppOpenAdManager {
String adUnitId = Platform.isAndroid
? 'ca-app-pub-3940256099942544/3419835294'
: 'ca-app-pub-3940256099942544/5662855259';
AppOpenAd? _appOpenAd;
bool _isShowingAd = false;
void loadAd() {
AppOpenAd.load(
adUnitId: adUnitId,
orientation: AppOpenAd.orientationPortrait,
request: AdRequest(),
adLoadCallback: AppOpenAdLoadCallback(
onAdLoaded: (ad) {
_appOpenAd = ad;
},
onAdFailedToLoad: (error) {
print('AppOpenAd failed to load: $error');
// Handle the error.
},
),
);
}
void showAdIfAvailable() {
if (!isAdAvailable) {
print('Tried to show ad before available.');
loadAd();
return;
}
if (_isShowingAd) {
print('Tried to show ad while already showing an ad.');
return;
}
// Set the fullScreenContentCallback and show the ad.
_appOpenAd!.fullScreenContentCallback = FullScreenContentCallback(
onAdShowedFullScreenContent: (ad) {
_isShowingAd = true;
print('$ad onAdShowedFullScreenContent');
},
onAdFailedToShowFullScreenContent: (ad, error) {
print('$ad onAdFailedToShowFullScreenContent: $error');
_isShowingAd = false;
ad.dispose();
_appOpenAd = null;
},
onAdDismissedFullScreenContent: (ad) {
print('$ad onAdDismissedFullScreenContent');
_isShowingAd = false;
ad.dispose();
_appOpenAd = null;
loadAd();
},
);
}
/// Whether an ad is available to be shown.
bool get isAdAvailable {
return _appOpenAd != null;
}
}
and here's
import 'app_open_ad_manager.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
/// Listens for app foreground events and shows app open ads.
class AppLifecycleReactor {
final AppOpenAdManager appOpenAdManager;
AppLifecycleReactor({required this.appOpenAdManager});
void listenToAppStateChanges() {
AppStateEventNotifier.startListening();
AppStateEventNotifier.appStateStream
.forEach((state) => _onAppStateChanged(state));
}
void _onAppStateChanged(AppState appState) {
// Try to show an app open ad if the app is being resumed and
// we're not already showing an app open ad.
if (appState == AppState.foreground) {
appOpenAdManager.showAdIfAvailable();
}
}
}
I was following this Tutorial and I got all the codes there.

Flutter - Async function not being waited for

appreciate the help! I've looked through some of the other responses on here and I can't find an answer.
I have a Provider, in which I have an async function defined. It reaches out to an external API, gets data, and then is meant to update the attributes in the Provider with the data received.
The Widget that uses the provider is meant to build a ListView with that data. projects is null until the response is received. That's why I need the async await functionality to work here. The error I'm getting says that "length can't be called on null", which means projects is still null at the time is reaches that line. That is because the async functionality isn't working.
Here is the Provider, in which my async function is defined:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../../constants/urls.dart';
import 'project.dart';
class Projects with ChangeNotifier{
List<Project> _projects;
List<Project> _myProjects;
final String authToken;
final List<Project> previousProjects;
final bool _initialLoad = true;
Projects(this.authToken, this.previousProjects);
List<Project> get projects {
return _projects;
}
List<Project> get myProjects {
return _myProjects;
}
bool get initialLoad {
return _initialLoad;
}
Future<void> fetchProjects() async {
print('inside future, a');
try {
var response = await http.get(
Uri.parse(Constants.fetchProjectsURL),
headers: {"Authorization": "Bearer " + authToken},
);
print('inside future, b');
if (response.statusCode == 200) {
final extractedData = json.decode(response.body) as List;
final List<Project> tempLoadedProjects = [];
extractedData.forEach((project) {
tempLoadedProjects.add(
Project(
// insert project params
),
);
});
_projects = tempLoadedProjects;
print(_projects);
print(projects);
notifyListeners();
} else {
print('something happened');
}
} catch (error) {
throw error;
}
}
}
Then, I used this provider in the following Widget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../providers/projects/projects_provider.dart';
class ProjectsColumn extends StatelessWidget {
Future<void> fetchProjects(ctx) async {
await Provider.of<Projects>(ctx).fetchProjects();
}
Widget build(BuildContext context) {
print('Before fetch');
fetchProjects(context);
print('After fetch');
final projects = Provider.of<Projects>(context, listen: false).projects;
return ListView.builder(
itemCount: projects.length,
itemBuilder: (BuildContext ctx, int index) {
return Card(
child: Text(
'Project Name:${projects[index]}',
),
);
});
}
}
Thoughts?
You need to put await before the method to a wait, but you can't do this in build() method, So you can use future builder like the answer of #jamesdlin
or you can call fetchProjects method in intState first like this way:
class ProjectsColumn extends StatefulWidget {
#override
State<ProjectsColumn> createState() => _ProjectsColumnState();
}
class _ProjectsColumnState extends State<ProjectsColumn> {
bool _isLoading = true;
Future<void> _fetchProjects() async {
await Provider.of<Projects>(context, listen: false).fetchProjects();
_isLoading = false;
if (mounted) setState(() {});
}
#override
void initState() {
super.initState();
_fetchProjects();
}
#override
Widget build(BuildContext context) {
return _isLoading
? const Center(child: CircularProgressIndicator())
: Consumer<Projects>(
builder: (context, builder, child) => builder.projects.isEmpty
? const Center(child: Text('No Projects Found'))
: ListView.builder(
shrinkWrap: true,
itemCount: builder.projects.length,
itemBuilder: (BuildContext ctx, int index) {
return Card(
child: Text(
'Project Name:${builder.projects[index]}',
),
);
},
),
);
}
}
EDIT:
a) From the docs HERE BuildContext objects are passed to WidgetBuilder functions (such as StatelessWidget.build), and are available from the State.context member., and in the previous example I used StatefulWidget widget that extends state class, then you can use context outside build but inside the class extends state, not like StatelessWidget.
b) mounted condition, it represents whether a state is currently in the widget tree, i used it to prevent the famous error: setState() called after dispose()
see docs HERE, also this useful answer HERE

Flutter web - Geolocator not working when uploaded to server

everyone.
I'm trying to develop a PWA with flutter 2.2.1 that shows a map using Mapbox_gl and displays the user current location using Geolocator.
So far everything works as expected while debuging the app, but when I run:
flutter build
or
flutter build --release
and then run
firebase deploy
the site gets uploaded, the map shows as intended and it asks for permissions but the user's location is never shown and Google Chrome's Console throws this error:
Uncaught TypeError: m.gfR is not a function
at Object.avh (main.dart.js:20405)
at main.dart.js:65755
at aiD.a (main.dart.js:5853)
at aiD.$2 (main.dart.js:34394)
at ahm.$1 (main.dart.js:34386)
at Rx.o1 (main.dart.js:35356)
at adi.$0 (main.dart.js:34770)
at Object.tQ (main.dart.js:5975)
at a5.mn (main.dart.js:34687)
at ada.$0 (main.dart.js:34731)
Here's the code I'm using on flutter:
mapbox.dart
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:geolocator/geolocator.dart';
import 'package:kkc/main.dart';
import 'package:mapbox_gl/mapbox_gl.dart';
import 'package:kkc/services/location_service.dart';
class Mapbox extends StatefulWidget {
const Mapbox();
#override
State createState() => MapboxState();
}
class MapboxState extends State<Mapbox> {
final Random _rnd = new Random();
Position? _currentLocation;
LatLng _currentCoordinates = new LatLng(0,0);
final List<_PositionItem> _positionItems = <_PositionItem>[];
StreamSubscription<Position>? _positionStreamSubscription;
late MapboxMapController _mapController;
List<Marker> _markers = [];
List<_MarkerState> _markerStates = [];
CameraPosition _kInitialPosition = CameraPosition(
target: LatLng(19.4274418, -99.1682147),
zoom: 18.0,
tilt: 70,
);
void _addMarkerStates(_MarkerState markerState) {
_markerStates.add(markerState);
}
void _onMapCreated(MapboxMapController controller) {
_mapController = controller;
controller.addListener(() {
if (controller.isCameraMoving) {
_updateMarkerPosition();
}
});
}
void _onStyleLoadedCallback() {
_updateMarkerPosition();
}
void _onCameraIdleCallback() {
_updateMarkerPosition();
}
void _updateMarkerPosition() {
final coordinates = <LatLng>[];
for (final markerState in _markerStates) {
coordinates.add(markerState.getCoordinate());
}
_mapController.toScreenLocationBatch(coordinates).then((points) {
_markerStates.asMap().forEach((i, value) {
_markerStates[i].updatePosition(points[i]);
});
});
}
void _addMarker(Point<double> point, LatLng coordinates) {
setState(() {
_markers.add(Marker(_rnd.nextInt(100000).toString(), coordinates, point, _addMarkerStates));
});
}
#override
void initState() {
super.initState();
_getCurrentLocation();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Stack(children: [
MapboxMap(
accessToken: Kukulcan.MAPBOX_ACCESS_TOKEN,
trackCameraPosition: true,
onMapCreated: _onMapCreated,
onCameraIdle: _onCameraIdleCallback,
onStyleLoadedCallback: _onStyleLoadedCallback,
initialCameraPosition: _kInitialPosition,
),
IgnorePointer(
ignoring: true,
child: Stack(
children: _markers,
))
]),
);
}
void _getCurrentLocation() async {
_currentLocation = await LocationService.startLocationService();
_currentCoordinates = new LatLng(_currentLocation!.latitude,_currentLocation!.longitude);
await _mapController.animateCamera(CameraUpdate.newLatLng(_currentCoordinates));
_addMarker(new Point(1, 1), _currentCoordinates);
if (_positionStreamSubscription == null) {
final positionStream = Geolocator.getPositionStream();
_positionStreamSubscription = positionStream.handleError((error) {
_positionStreamSubscription?.cancel();
_positionStreamSubscription = null;
}).listen((position) => setState(() => _positionItems.add(
_PositionItem(_PositionItemType.position, position.toString()))));
_positionStreamSubscription?.pause();
}
}
}
class Marker extends StatefulWidget {
final Point _initialPosition;
LatLng _coordinate;
final void Function(_MarkerState) _addMarkerState;
Marker(
String key, this._coordinate, this._initialPosition, this._addMarkerState)
: super(key: Key(key));
#override
State<StatefulWidget> createState() {
final state = _MarkerState(_initialPosition);
_addMarkerState(state);
return state;
}
}
class _MarkerState extends State with TickerProviderStateMixin {
final _iconSize = 80.0;
Point _position;
_MarkerState(this._position);
#override
Widget build(BuildContext context) {
var ratio = 1.0;
//web does not support Platform._operatingSystem
if (!kIsWeb) {
// iOS returns logical pixel while Android returns screen pixel
ratio = Platform.isIOS ? 1.0 : MediaQuery.of(context).devicePixelRatio;
}
return Positioned(
left: _position.x / ratio - _iconSize / 2,
top: _position.y / ratio - _iconSize / 2,
child: Image.asset('assets/img/pin.png', height: _iconSize));
}
void updatePosition(Point<num> point) {
setState(() {
_position = point;
});
}
LatLng getCoordinate() {
return (widget as Marker)._coordinate;
}
}
enum _PositionItemType {
permission,
position,
}
class _PositionItem {
_PositionItem(this.type, this.displayValue);
final _PositionItemType type;
final String displayValue;
}
Does anyone have an idea on what's the problem?
Cheers!
Anyway the solution i found is to use --no-sound-null-safety argument as stated by geolocat documentation
I quote:
NOTE: due to a bug in the dart:html library the web version of the Geolocator plugin does not work with sound null safety enabled and compiled in release mode. Running the App in release mode with sound null safety enabled results in a Uncaught TypeError (see issue #693). The current workaround would be to build your App with sound null safety disabled in release mode:

Detect Mock Location is enabled or disabled in Flutter

My question is that I am using flutter platform to develop an app for my client and I want that my developed app should be able to be detect mock location status from android phone settings so I can check whether the location is coming from gps provider or mock location app. And if mock location is enabled then my app should throw an error msg
i had the same problem and i fixed it by coding in java and implement in flutter project.
here is what i did:
1) add this to your Main_Activity in flutter project.
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import java.util.List;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.io/location";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
#Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getLocation")) {
boolean b = getMockLocation();
result.success(b);
} else {
result.notImplemented();
}
}
});
}
public static boolean isMockSettingsON(Context context) {
// returns true if mock location enabled, false if not enabled.
if (VERSION.SDK_INT >= VERSION_CODES.CUPCAKE) {
if (Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ALLOW_MOCK_LOCATION).equals("0"))
return false;
else
return true;
}
return false;
}
public static boolean areThereMockPermissionApps(Context context) {
int count = 0;
PackageManager pm = context.getPackageManager();
List<ApplicationInfo> packages =
pm.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo applicationInfo : packages) {
try {
PackageInfo packageInfo = pm.getPackageInfo(applicationInfo.packageName,
PackageManager.GET_PERMISSIONS);
// Get Permissions
String[] requestedPermissions = packageInfo.requestedPermissions;
if (requestedPermissions != null) {
for (int i = 0; i < requestedPermissions.length; i++) {
if (requestedPermissions[i]
.equals("android.permission.ACCESS_MOCK_LOCATION")
&& !applicationInfo.packageName.equals(context.getPackageName())) {
count++;
}
}
}
} catch (PackageManager.NameNotFoundException e) {
Log.e("Got exception " , e.getMessage());
}
}
if (count > 0)
return true;
return false;
}
private boolean getMockLocation() {
boolean b ;
b= areThereMockPermissionApps(MainActivity.this);
return b;
}
}
2) use it in your flutter_dart Code like this:
static const platform = const MethodChannel('samples.flutter.io/location');
bool mocklocation = false;
Future<void> _getMockLocation() async {
bool b;
try {
final bool result = await platform.invokeMethod('getLocation');
b = result;
} on PlatformException catch (e) {
b = false;
}
mocklocation = b;
}
if (mocklocation == true) {
return showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: (){},
child: AlertDialog(
title: Text('Location'),
content: Text('Your Location is fake'),
),
);
});
}
3) for more information and example:
https://flutter.dev/docs/development/platform-integration/platform-channels
Barzan's answer is very good, there's also a Flutter package named trust_location, you can find it here.
You can use it as following to check mock location:
bool isMockLocation = await TrustLocation.isMockLocation;
So, I recommend to use it.

How do I open a specific page on onesignal notification click on flutter?

I am using OneSignal push notification service and I want to open the app directly to specific page on notification click. I am sending the page through data. I tried navigator.push but it didn't work i guess because of context issue. I am calling _initializeonesignal() after login which contains onesignal init and the following code.
OneSignal.shared.setNotificationOpenedHandler((notification) {
var notify = notification.notification.payload.additionalData;
if (notify["type"] == "message") {
//open DM(user: notify["id"])
}
if (notify["type"] == "user") {
//open Profileo(notify["id"])
}
if (notify["type"] == "post") {
//open ViewPost(notify["id"])
}
print('Opened');
});
You will need to register a global Navigator handle in your main application scaffold -- then you can use it in your notification handlers..
So -- in our app in our main App we have :
// Initialize our global NavigatorKey
globals.navigatorKey = GlobalKey<NavigatorState>();
...
return MaterialApp(
title: 'MissionMode Mobile',
theme: theme,
initialRoute: _initialRoute,
onGenerateRoute: globals.router.generator,
navigatorKey: globals.navigatorKey,
);
The key is the navigatorKey: part and saving it to somewhere you can access somewhere else ..
Then in your handler:
OneSignal.shared.setNotificationOpenedHandler(_handleNotificationOpened);
...
// What to do when the user opens/taps on a notification
void _handleNotificationOpened(OSNotificationOpenedResult result) {
print('[notification_service - _handleNotificationOpened()');
print(
"Opened notification: ${result.notification.jsonRepresentation().replaceAll("\\n", "\n")}");
// Since the only thing we can get current are new Alerts -- go to the Alert screen
globals.navigatorKey.currentState.pushNamed('/home');
}
That should do the trick -- does for us anyway :)
It's simple, by using onesignal, you can create system call from kotlin to flutter
In my case, I had to take the data in the URL from a notification that comes from onesignal in WordPress:
package packageName.com
import android.os.Bundle
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
// import io.flutter.plugins.firebaseadmob.FirebaseAdMobPlugin;
private val CHANNEL = "poc.deeplink.flutter.dev/channel"
private var startString: String? = null
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "initialLink") {
if (startString != null) {
result.success(startString)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = getIntent()
startString = intent.data?.toString()
}
}
This I'm taking data from onCreate, yet only when clicking on the notification, I will take the "intent" data and then I will send it to my flutter code in the following class:
import 'dart:async';
import 'package:flutter/services.dart';
class MyNotificationHandler {
//Method channel creation
static const platform =
const MethodChannel('poc.deeplink.flutter.dev/channel');
//Method channel creation
static String url;
static String postID;
static onRedirected(String uri) {
url = uri;
postID = url.split('/').toList()[3];
}
static Future<String> startUri() async {
try {
return platform.invokeMethod('initialLink');
} on PlatformException catch (e) {
return "Failed to Invoke: '${e.message}'.";
}
}
//Adding the listener into contructor
MyNotificationHandler() {
//Checking application start by deep link
startUri().then(onRedirected);
}
}
Here I'm taking data from a WordPress URL, the last word after the 4ed '/' which is the id of the post.
now how to use it and call it, as I created it static I will use it in my code when the first page loads,
import 'package:com/config/LocalNotification.dart';
class MyLoadingPage extends StatefulWidget {
MyLoadingPage() {
MyNotificationHandler.startUri().then(MyNotificationHandler.onRedirected);
}
#override
_MyLoadingPageState createState() => _MyLoadingPageState();
}
...
This page will load the data from my WordPress API.
so after loading the data from the database, I will check if a value of the id, and navigate to the article page, the example in my home page:
....
#override
void initState() {
MyViewWidgets.generalScaffoldKey = _scaffoldKey;
myWidgetPosts = MyPostsOnTheWall(MyPost.allMyPosts, loadingHandler);
MyHomePAge.myState = this;
super.initState();
if (MyNotificationHandler.postID != null) {
Future.delayed(Duration(milliseconds: 250)).then((value) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyArticlePage(MyPost.allMyPosts
.firstWhere((element) =>
element.id == MyNotificationHandler.postID))));
});
}
}
....
The secrete is in kotlin or Java by using that call from kotlin to fluter or from java to flutter, I think you will have to do the same with ios, I will leave an article that helped me.
https://medium.com/flutter-community/deep-links-and-flutter-applications-how-to-handle-them-properly-8c9865af9283
I resolved the same problems, as below:
In the main screen file MyApp.dart
#override
void initState() {
OneSignalWapper.handleClickNotification(context);
}
OneSignalWapper.dart :
static void handleClickNotification(BuildContext context) {
OneSignal.shared
.setNotificationOpenedHandler((OSNotificationOpenedResult result) async {
try {
var id = await result.notification.payload.additionalData["data_id"];
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => PostDetailsScreen.newInstance('$id')));
} catch (e, stacktrace) {
log(e);
}
});
}
You can use this Code:
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
OneSignal.shared.setNotificationOpenedHandler((result) {
navigatorKey.currentState.push(
MaterialPageRoute(
builder: (context) => YourPage(),
),
);
});
MaterialApp(
home: SplashScreen(),
navigatorKey: navigatorKey,
)
I find the solution:
On your home screen, set the handler. And, before this, set on your configuration notification this way
First:
Map<String, dynamic> additional = {
"route": 'detail',
"userId": widget.userId
};
await OneSignal.shared.postNotification(OSCreateNotification(
playerIds: userToken,
content: 'your content',
heading: 'your heading',
additionalData: additional,
androidLargeIcon:'any icon'));
Second:
OneSignal.shared.setNotificationOpenedHandler(
(OSNotificationOpenedResult action) async {
Map<String, dynamic> dataNotification =
action.notification.payload.additionalData;
if (dataNotification.containsValue('detailPage')) {
await Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new DetailScreen(
userId: dataNotification['userId'],
),
).catchError((onError) {
print(onError);
});
}