How do I get PendingIntent using the MvvmCross 5 IMvxNavigationService? - c#

I have a method I used in MvvmCross 4.x that was used with the NotificationCompat.Builder to set a PendingIntent of a notification to display a ViewModel when the notification is clicked by the user. I'm trying to convert this method to use the MvvmCross 5.x IMvxNavigationService but can't see how to setup the presentation parameters, and get a PendingIntent using the new navigation API.
private PendingIntent RouteNotificationViewModelPendingIntent(int controlNumber, RouteNotificationContext notificationContext, string stopType)
{
var request = MvxViewModelRequest<RouteNotificationViewModel>.GetDefaultRequest();
request.ParameterValues = new Dictionary<string, string>
{
{ "controlNumber", controlNumber.ToString() },
{ "notificationContext", notificationContext.ToString() },
{ "stopType", stopType }
};
var translator = Mvx.Resolve<IMvxAndroidViewModelRequestTranslator>();
var intent = translator.GetIntentFor(request);
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTask);
return PendingIntent.GetActivity(Application.Context,
_notificationId,
intent,
PendingIntentFlags.UpdateCurrent);
}
The RouteNotificationViewModel does appear when I click the notification but Prepare and Initialize are not being called. What is necessary to convert this method from MvvmCross 4.x style of navigation to MvvmCross 5.x style of navigation?

It's possible to do this in MvvmCross 5+ but it's not as clean as it previously was.
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 two places to handle this Intent from MainActivity while still allowing the use of MvvmCross navigation service:
If the app was not running while the notification was clicked then OnCreate will 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.
}
}
If the app was running while the notification was clicked then OnNewIntent will 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.
}
}

Related

Xamarin.Forms Popup Page from background service

I am working with Xamarin.Forms on a project which is required to have a background service that will lookup for a date to remind the user of some important activity, but has to behave like an alarm and to be like a popup page or some sort of where I can make some XAML and bind some pieces of information from SQLite.
I have made a service that works in the background, I have made a subscription via MessagingCenter, and all is working so well the only thing I can't do is just from the background to simply open designed page bindings and VM will do the rest of the job.
I spent 5 days searching around but nothing seems to work...
Help will be well appreciated.
I have found a different solution to my problem. using AlarmManager here is a very simple code that will just start the new activity I have just created a new constructor in app.xaml.cs that will open the reminder page as the main page. and LoadApp will be called from ReminderActivity but with
LoadApplication(new App(true));
void LoadAlarm()
{
var alarmIntent = new Intent(this, typeof(AlarmReceiver));
var pending = PendingIntent.GetBroadcast(this, 0, alarmIntent, PendingIntentFlags.UpdateCurrent);
var alarmManager = GetSystemService(AlarmService).JavaCast<AlarmManager>();
alarmManager.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + 600 * 1000, pending);
}
and
[BroadcastReceiver]
public class AlarmReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
intent = new Intent(Android.App.Application.Context, typeof(ReminderActivity));
intent.AddFlags(ActivityFlags.NewTask | ActivityFlags.ClearTask);
Android.App.Application.Context.StartActivity(intent);
}
}

xamarin forms app link - open PCL view from ios app delegate

In a Xamarin Forms cross platform app I can open the app from an external email app link.
It opens in android just fine, by adding an intent to the manifest, then within the activity which starts, I create another intent to fire the main activity
public class AppLinkActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
string code = null;
if (Intent.Data.Query != null)
{
code = Intent.Data.Query.Substring(Intent.Data.Query.LastIndexOf('=') + 1);
}
if (Intent.Data != null)
{
var uri = Intent.Data;
if (uri != null)
{
Intent i = new Intent(this, typeof(MainActivity));
i.AddFlags(ActivityFlags.ReorderToFront);
i.PutExtra("code", code);
i.PutExtra("flag", true);
this.StartActivity(i);
}
}
this.FinishActivity(0);
}
}
In ios, the applink triggers the override of OpenUrl in the app delegate, but I'm not sure how to navigate to a particular PCL page from here, what happens is the app opens at it's last open page
public override bool OpenUrl(UIApplication app, NSUrl url, string sourceApp, NSObject annotation)
{
string _uri = url.ToString();
string code = _uri.Substring(_uri.LastIndexOf('=') + 1);
LoadApplication(new App(true, code));
return true;
}
Can anyone point me in the right direction with this? All I really need to do is, from the OpenUrl method, navigate to a view within the PCL
for anyone interested, I sorted this by replacing
LoadApplication(new App(true, code));
with
App.Current.MainPage = enterPin();
which calls
public Page enterPin()
{
return new EnterPinPage(SimpleIoc.Default.GetInstance<ISecureStorage>(), code, 1);
}

Xamarin Android Activity

I am able to press the back button to from LAYOUT2 back to LAYOUT1.
But how would I go from LAYOUT3 to LAYOUT2 to LAYOUT1?
I have tried doing different things such as this
In the MainActivity I have this
public override void OnBackPressed()
{
var intent = new Intent(this, typeof(MainActivity));
StartActivity(intent);
}
In the SecondActivity I have this
public override void OnBackPressed()
{
var intent = new Intent(this, typeof(Menu));
StartActivity(intent);
}
With that code above it just stays on the SecondActivity and doesn't go back to the MainActivity - why?
By default app behaviour is simply go to last activity.
you can try without any extra coding.
I think nothing will be needed.
Remove your overrides for the OnBackPressed and the behaviour you're expecting will be achieved.

Xamarin.Android app resumed by shortcut intent - Master and Detail must be set before adding MasterDetailPage to a container

I have Xamarin.Android todo list mobile app using Prism.
The problem is:
In android system, I can create shortcut to open specific list in
app.
When I open app, and press home button, it remains on background
(thats ok)
When I then run app from desktop shortcut, it opens
android activity and when I create new PrismApplication (
LoadApplication(new App()); ) everything is running OK, but after
creating viewmodel for view, app is still using old viewmodel from
before.
I made this workaroud and I use same instance of PrismApplication:
static App xamApp;
protected override void OnCreate(Bundle bundle)
{
if (xamApp == null)
{
Forms.Init(this, bundle);
xamApp = new App();
}
LoadApplication(xamApp);
xamApp.Redirect(Intent.GetStringExtra("ListID"));
}
Now, problem is redirecting. This code:
public void Redirect(string listId)
{
NavigationService.NavigateAsync($"MainPage/MainNavigationPage/TodoList?id={listId}", animated: false);
}
leads to the error:
System.InvalidOperationException: Master and Detail must be set before adding MasterDetailPage to a container.
Prism should take care of Binding of Detail in MasterDetailPage by the "TodoList" from NavigateAsync uri.
Does enyone know what can be the problem here?
So I finally got it working.
First I used LaunchMode = LaunchMode.SingleTask in my ActivityAttribute of MainActivity
[Activity(Label = "..", LaunchMode = LaunchMode.SingleTask, Icon = "#drawable/icon", Theme = "#style/MainTheme", MainLauncher = true]
public class MainActivity : FormsAppCompatActivity
Then I used OnNewIntent method of FormsAppCompatActivity so after app is on backgroud, only this event is launched :
protected override void OnNewIntent(Intent intent)
{
var listId = intent.GetStringExtra("ListID");
((App)App.Current).Redirect(listId);
}
Now even $"MainNavigationPage/TodoList?id={listId}" works
Based on the info you provided, I am assuming that when the app is launched again, it is already running, your previous MasterDetail page is already on the stack. IN your reset method, you want to reset your navigation stack to the new uri passing in the parameter. IN this case, you should use an absolute uri. This means try adding a "/" prefix to your uri. So something like this:
public void Redirect(string listId)
{
NavigationService.NavigateAsync($"/MainPage/MainNavigationPage/TodoList?id={listId}", animated: false);
}

Start activity from constructor of other activity?

I'm pretty new to xamarin / android development but this is pretty much what I'm trying conceptually:
Have a main activity that checks whether or not a user is already logged in
If the user is logged in, start the MainAppActivity
If the user is not logged in, start the OnboardingActivity
The reason I think these should be in different activities is for general design reasons (separation of concerns etc) - but also because both activities have different themes. (The Onboarding process has no title bar)
In code, this should be pretty much what I need:
[Activity(Label = "MyApp", MainLauncher = true, Icon = "#drawable/icon", Theme = "#android:style/Theme.Black.NoTitleBar")]
public class MainActivity : Activity
{
private readonly TransactionService _transactionService;
public MainActivity()
{
var isLoggedIn = true; // This will be loaded from somewhere
if(isLoggedIn)
{
var intent = new Intent(this, typeof(MainAppActivity));
StartActivity(intent);
}
else
{
var intent = new Intent(this, typeof(OnboardingActivity));
StartActivity(intent);
}
}
}
However, this code just throws an unspecified exception.
If I try the same thing during the protected override void OnCreate(Bundle bundle) process, I get an exception as well.
However, when I bind something like that to a button event and click it manually, it will work. (That is not my intent, but to test there is nothing wrong with my activities)
So, question is, how do I do something like this?
Edit: Here is the stacktrace if I try it in the constructor:
0x29 in System.Diagnostics.Debugger.Mono_UnhandledException_internal C#
0x1 in System.Diagnostics.Debugger.Mono_UnhandledException at /Users/builder/data/lanes/1978/f98871a9/source/mono/mcs/class/corlib/System.Diagnostics/Debugger.cs:122,4 C#
0x6 in Android.Runtime.UncaughtExceptionHandler.UncaughtException at /Users/builder/data/lanes/1978/f98871a9/source/monodroid/src/Mono.Android/src/Runtime/UncaughtExceptionHandler.cs:35,4 C#
0x1C in Java.Lang.Thread.IUncaughtExceptionHandlerInvoker.n_UncaughtException_Ljava_lang_Thread_Ljava_lang_Throwable_ at /Users/builder/data/lanes/1978/f98871a9/source/monodroid/src/Mono.Android/platforms/android-21/src/generated/Java.Lang.Thread.cs:221,5 C#
0x1D in object.b8bd1d31-3e2e-454d-bd94-9d5dea40eddb C#
I have not used xamarin, so I could be wrong.
FYR, on native android, you should do that in the onCreate of your MainActivity instead of Constructor

Categories