[Android] Navigating to ViewModel from Android notification using PendingIntent
I've made a simple Android project demonstrating the problem.
I asked a question on StackOverflow and was asked to create an issue here.
The problem I have is I can't figure out how to navigate to a MvxViewModel using a PendingIntent. This was doable in MvvmCross 4.x. The same MvvmCross 4.x techniques work in MvvmCross 5.x, but the documentation states it's not good to mix the two navigation schemes together.
Steps to reproduce :scroll:
-
Create an Android notification using
Notification.BuilderorNotificationCompat.Builder. -
Create an
MvxViewModelRequestfor the ViewModel. -
Get a
PendingIntentfrom theMvxViewModelRequest. -
Associate the
PendingIntentwith the Android notification usingSetContentIntent. -
Click on the notification. The activity is displayed but the new MvvmCross 5.x lifecycle is not fully implemented. Specifically
Prepareis not called.
Expected behavior :thinking:
Prepare should be called.
Actual behavior :bug:
Prepare is not called, so the ViewModel is essentially uninitialized. The following output can be observed:
2017-11-28 04:10:00 [TRACE] (MvxNotificationNavigation.Core.ViewModels.NewViewModel) NewViewModel.ctor called 11-28 16:10:00.373 I/mvx ( 4616): 34.28 Missing parameter for call to NewViewModel - missing parameter parameter - asssuming null - this may fail for value types! [0:] mvx:Diagnostic: 34.28 Missing parameter for call to NewViewModel - missing parameter parameter - asssuming null - this may fail for value types! 2017-11-28 04:10:00 [TRACE] (MvxNotificationNavigation.Core.ViewModels.NewViewModel) NewViewModel.Initialize called
Configuration :wrench:
Version: 5.5
Platform:
- [ ] :iphone: iOS
- [x] :robot: Android
- [ ] :checkered_flag: WPF
- [ ] :earth_americas: UWP
- [ ] :apple: MacOS
- [ ] :tv: tvOS
- [ ] :monkey: Xamarin.Forms
@tbalcom Did you really see this issue in v5.5? Because this PR: #2359 should have fixed the fact that Prepare / Initialize were not being called.
The generic public override void Prepare is called in Mvx 5.5 but not the override with the navigation parameters public override void Prepare(NotificationModel parameter).
I've found I can use a PendingIntent which triggers a BroadcastReceiver which then uses the IMvxNavigationService to navigate to the ViewModel when an Android notification is clicked. It's not ideal at all but it works.
Is there any MvvmCross 5 or 6 sample showing how to navigate to a ViewModel when an Android notification is clicked using the IMvxNavigationService?
So... in order to fix this issue, we will need to:
- Provide a new convention to store the provided parameter inside the Intent bundle.
- Check if the parameter content is serializable, throw an exception if it isn't. Also document this.
- Read the parameter from the bundle, deserialize and find a way to call
Prepare<T>.
Btw thanks for the input and update!
Regarding your question, I don't think there's an example unfortunately... But you should be able to create a custom dependency service (interface & implementation at Core level) that handles incoming notifications. You just need to:
- Ensure Mvx is running.
-
Mvx.Resolveyour service (or something like that) and pass the notification information to it. - Inside your service, navigate to wherever you want.
@nmilcoff would you be able to generate a PR that demonstrates this issue in the playground?
@nmilcoff I did another trick, but have a different issue. I created a custom BroadcastReceiver and generated a pending intent from GCMService. When I press on notification, my BroadcastReceiver intercepts it and navigates to specific viewModel. It works fine until I update to latest MvvmCross 6.2.1. Right now BroadcastReceiver does not understand which activity is the top one.
I found a breaking change in MvxApplicationCallbacksCurrentTopActivity implementation. What do you think would be the best workaround? If I create a custom one , could it cause any other issues when activity in background is still top activity ?
@Nickolas- What version of Mvx were you using before? I would probably start by trying to remove the dependency on IMvxCurrentTopActivity in the BroadcastReceiver, as you can actually use IMvxNavigationService outside a ViewModel
I’m not using IMvxCurrentTopActivity in the BroadcastReceiver.
NavigationService.Navigate<SomeVM> (data);
There is no top current activity when the app is in the background state.
But I found the problem in my logic. I was trying to present a MvxFragmentViewModel, but with no top current activity the presenter could not understand how to show it. Then I added the logic back. Now it shows the correct activity even in the background state. But it will not bring the activity to the front. I need to use Activity.LaunchMode = SingleTop + NewIntentHandle. You should document this one too, maybe someone will have a similar problem.
@Nickolas- I have the exact same issue as you. When the app is in the background I cannot get it to resume the app and display the activity. Only when the app is opened then it navigates correctly from the broadcast receiver. I'm also just using NavigationService.Navigate<SomVm>(data) but my activity/vm doesn't contain fragments or isn't a fragment. When the navigate is called the VM initialized is called but after that I get the logs: 2019-01-22 02:25:13 [WARN] (MvvmCross.Logging.MvxLog) Cannot Resolve current top activity
and the app is not opened
How were you able to resume the app when calling the navigate from the broad cast receiver?
I ended up implementing my own workaround. Since navigation will work in onresume in an activity what I do is I call a different activity with a regular pending intent and navigate from there into the activity I really want to go. Simple, not perfect but works for what I need to do. Looking forward for the top activity fix though.
Here's a working solution for an Android app using one activity and multiple fragments:
For starters you want to specify the singleTop launch mode for your activity:
[Activity(LaunchMode = LaunchMode.SingleTop, ...)]
public class MainActivity : MvxAppCompatActivity
Generate the notification PendingIntent like this:
var intent = new Intent(Context, typeof(MainActivity));
intent.AddFlags(ActivityFlags.SingleTop);
// Putting an extra in the Intent to pass data to the MainActivity
intent.PutExtra("from_notification", true);
var pendingIntent = PendingIntent.GetActivity(Context, notificationId, intent, 0);
Now there are places to handle this Intent from MainActivity while still allowing the use of MvvmCross navigation service:
-
OnCreate- If the app was not running while the notification was clicked thenOnCreatewill be called.
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
if (bundle == null && Intent.HasExtra("from_notification"))
{
// The notification was clicked while the app was not running.
// Calling MvxNavigationService multiple times in a row here won't always work as expected. Use a Task.Delay(), Handler.Post(), or even an MvvmCross custom presentation hint to make it work as needed.
}
}
-
OnNewIntent- If the app was running while the notification was clicked thenOnNewIntentwill be called.
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
if (intent.HasExtra("from_notification"))
{
// The notification was clicked while the app was already running.
// Back stack is already setup.
// Show a new fragment using MvxNavigationService.
}
}