Alert Dialog in ViewModel - MVVMCross - c#

In the ViewModel, I have Save method where I check isValid property.
If isValid is false, then I want to display an error message.
Since AlertDialog is platform specific, I wonder how do you handle that situation in the ViewModel?
public void Save()
{
if (isValid)
{
OnExit(this, null);
}
else
{
//issue an alert dialog here
}
}
Update
I have used the following plugin and added the following line of code as follows, but it throws an error.
else
{
Mvx.Resolve<IUserInteraction>().Alert("it is not valid");
}
Update 2
Chance.MvvmCross.Plugins.UserInteraction is a namespace but it is used as a type error.
Update 3
I have added Acr.UserDialogs plugin and called as follows, but I have got the same error.
Mvx.Resolve<IUserDialogs>().Alert("it is not valid");

Using ACR User Dialogs is the simplest approach.
In your App.cs (Core/PCL) you will need to register the interface:
public class App : MvxApplication
{
public override void Initialize()
{
// Example Other registrations
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
Mvx.RegisterSingleton<IUserDialogs>(() => UserDialogs.Instance);
}
}
Then you can call your alert form your ViewModel.
Mvx.Resolve<IUserDialogs>().Alert("it is not valid");
Note for Android Platform support
Then if you are supporting Android you will need to initialize UserDialog with an instance of the activity context. This will have to be done in each activity that you will be making use of UserDialogs or if you have a shared base activity you can do it there.
[Activity]
public class MainActivity : MvxActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.activity_main);
// Initialize Acr UserDialogs
UserDialogs.Init(this);
}
}
Alternatively
You can follow the Mvvmcross document on using platform specific implementations of an interface if you need a more custom modal implementation.

This is how I handle the Alert messages in the viewmodel. Try this.
await App.Current.MainPage.DisplayAlert("Active subscription required", "You do not have an active subscription for Part 2 exams", "OK");

There is an existing MvvmCross plugin called User Interaction that allows displaying alerts and collecting inputs from ViewModels.
From the author BrianChance:
Really simple, easy, beautiful ways to show a message box or to collect user input from your ViewModels
Check it out here and NuGet Link Here.
To install the plugin, make sure you override LoadPlugins in your SetUp Class on iOS and Android (and windows phone) like so:
public override void LoadPlugins(MvvmCross.Platform.Plugins.IMvxPluginManager pluginManager)
{
base.LoadPlugins(pluginManager);
pluginManager.EnsurePluginLoaded<Chance.MvvmCross.Plugins.UserInteraction>();
}

My approach is that i use an event for this scenario. My base class for my view models has a EventHandler OnUserNotification, where the views can kinda subscribe to. The UserNotificationType is just an enum and i let the view kinda decide how it reacts to the situation.
The property:
public EventHandler<UserNotificationType> OnUserNotification { get; set; }
The call:
if (OnUserNotification != null)
{
OnUserNotification.Invoke(this, UserNotificationType.ENetworkError);
}
In the view:
private void onUserNotification(object sender, UserNotificationType userNotificationType)
{
// Do Something like showing a Snackbar, AlertDialog, etc...
}
Of course you can make the eventtype more complex if needed.
Didnt try the plugin which got suggested by wishmaster though, so that might be a smoother implementation?

Use Acr.UserDialogs. There is a great examples on github
You can grab it on nuget
It works well with dependency injection or a static singleton UserDialogs.Instance

Related

Event when Xamarin page is FULLY loaded?

community.
In advance, I apologize if this really has a simple solution. I have a troublesome problem, considering my skill level with Xamarin, that I need to get resolved.
The problem is it seems like Xamarin doesn't have an event/function/etc to call when a page is FULLY displayed, as in you can see everything the page is supposed to display, and then that function is called. Quick note, what does not work for me is the OnAppearing function because it fires off too early before the screen is visible.
I was attempting to decipher a solution here. In this post, a user answers with the following code.
Here is step 1):
private bool _toggleTemp;
public bool ToggleTemp
{
get => _toggleTemp;
set => SetProperty(ref _toggleTemp, value);
}
Step 2)
LoadingVm.ToggleTemp = true;
Step 3)
<Switch IsToggled="{Binding ToggleTemp}" Toggled="Switch_OnToggled" IsVisible="False" />
Step 4)
private async void Switch_OnToggled(object sender, ToggledEventArgs e)
{
/* Your code goes here... */
}
First concern. I created a ViewModel file, it's inside of a folder called "ViewModels", and the user who posted the code in the link said to create a property in the view model, for some reason I'm getting an error stating "the name set property does not exist in the current context". So is it alright I swap that code out and just put the following instead? I mean it SEEMS like the same thing, right?
public bool Temp
{
get { return _toggleTemp; }
set { _toggleTemp = value; }
}
Second concern. I have no clue what "LoadingVm" is in his code. It doesn't come up for me. Is it because I'm missing a "using" at the top? But regardless, the whole code line was this "LoadingVm.ToggleTemp = true;", so he's just calling the function to set it to be true.
In the end, I'm ASSUMING that this will no doubt help me get the code working where I can do whatever I please AFTER the page is completely loaded, correct? I don't really see too many people disagreeing with the method the guy in the link has given, but if so, feel free to give other suggestions.
Again, please forgive me if this is a simple error. I'm still getting used to Xamarin and C# is still somewhat fresh in my mind from not having used it in a while.
You can use custom renderer of ContentPage to know if the view is fully loaded and then use MessagingCenter to notify your shared project:
In iOS:
[assembly:ExportRenderer (typeof(ContentPage), typeof(CameraPageRenderer))]
namespace App362.iOS
{
public class CameraPageRenderer : PageRenderer
{
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
Console.WriteLine("ViewDidAppear");
MessagingCenter.Send<object>(new object(), "ViewLoaded");
}
}
}
In Android:
[assembly: ExportRenderer(typeof(ContentPage), typeof(CameraPageRenderer))]
namespace App362.Droid
{
[Obsolete]
public class CameraPageRenderer : PageRenderer{
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
Console.WriteLine("OnAttachedToWindow");
MessagingCenter.Send<object>(new object(), "ViewLoaded");
}
}
}
In Shared project:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
Console.WriteLine("OnAppearing");
MessagingCenter.Subscribe<object>(new object(), "ViewLoaded", (sender) =>
{
// Do something whenever the "ViewLoaded" message is received
Console.WriteLine("Do something whenever the ViewLoaded message is received");
});
}
}

Android Things with Xamarin Issue with IGpioCallback

I'm just starting with Android Things with Xamarin, and I've already successfully turned on a LED, but I'm having trouble to detect a push button input.
I think the problem is the "RegisterGpioCallback" in the code below, but I'm not sure and really don't know how to fix it. Can somebody help me?? This is the code I'm using:
public class BlinkActivity : Activity
{
private IGpio gpio;
private IGpio button;
private IGpioCallback mButtonCallback;
protected override void OnCreate(Bundle savedInstanceState)
{
this.mButtonCallback = mButtonCallback;
PeripheralManager peripheralManager = PeripheralManager.Instance;
gpio = peripheralManager.OpenGpio("BCM17");
gpio.SetDirection(Gpio.DirectionOutInitiallyLow);
gpio.Value = false;
button = peripheralManager.OpenGpio("BCM4");
button.SetDirection(Gpio.DirectionIn);
button.SetEdgeTriggerType(Gpio.EdgeNone);
button.RegisterGpioCallback(new Handler(), mButtonCallback);
base.OnCreate(savedInstanceState);
Task.Run(() =>
{
if (mButtonCallback.OnGpioEdge(button) == true)
{
gpio.Value = !gpio.Value;
}
});
}
}
You need to actually implement the IGpioCallback interface so the com.google.android.things.pio library can make a "call back" into your application when the value of the GPIO changes.
Assign the RegisterGpioCallback to the actual object instance that has implemented the interface, in the following example, that will be on the Activity.
public class BlinkActivity : Activity, IGpioCallback
{
~~~~
button.RegisterGpioCallback(new Handler(), this);
~~~~
// remove the Task.Run block
public OnGpioEdge(Gpio gpio)
{
Log.Debug("SO", gpio.Value.ToString());
}
~~~~
}
I had some issues following this in Maui. I'd created an IGPIO interface in the shared code, and then a platform-specific GPIO class inside the Android platform code. The code would run, but then crash when it got to the Registration of the callback. The error said I had to pass a Java.Lang.Object or Java.Lang.Throwable as argument 2 to com.google.android.things.pio.impl.GpioImpl.registerGpioCallback(android.os.Handler, com.google.android.things.pio.GpioCallback).
I tried using each of these as the base class for my GPIO class, but then the app wouldn't build. When I'd autogenerated the IGpioCallback interface implementation in the class it had created a dispose method and a Handle property along with the OnGpioEdge callback method. Removing these allowed the app to work properly. so my class definition ended up looking something like this for the registration and event:
public class GPIO : Java.Lang.Throwable, IGPIO, IGpioCallback
{
public event EventHandler OnButtonEdge;
IGpio ButtonPin;
public void registerPinForEdgeDetection(string pinName)
{
using (var peripheralManager = PeripheralManager.Instance)
{
ButtonPin = peripheralManager?.OpenGpio(pinName);
ButtonPin.SetDirection(Gpio.DirectionIn);
ButtonPin.SetEdgeTriggerType(Gpio.EdgeBoth);
ButtonPin.RegisterGpioCallback(new Android.OS.Handler(), this);
}
}
public bool OnGpioEdge(IGpio gpio)
{
OnButtonEdge?.Invoke(ButtonPin, EventArgs.Empty);
return true;
}
}

How to clear data of ViewModel in MVVM Light xamrin?

I am working on Xamrin Form right now. I have problem with clear data of ViewModel.
When I logout and login with different user, it shows me data of previous user because the value of UserProfileViewModel doesn't get clear.
When user logout, I want to clear user data from UserProfileViewModel class file. Currently I do this manually when user click on logout. I want any default method like dispose to clear all class member.
I have tried to inherit IDisposable interface with this.Dispose(); but that also didn't work.
I have also tried with default constructor as following but it throws error of
`System.TypeInitializationException`
on this line in app.xaml.cs: public static ViewModelLocator Locator => _locator ?? (_locator = new ViewModelLocator());
public UserProfileViewModel()
{
//initialize all class member
}
In given code, you can see that on Logout call, I call method
`ClearProfileData` of `UserProfileViewModel`
which set default(clear)
data. It is manually. I want to clear data when user logout.
View Model Logout Page
[ImplementPropertyChanged]
public class LogoutViewModel : ViewModelBase
{
public LogoutViewModel(INavigationService nService, CurrentUserContext uContext, INotificationService inService)
{
//initialize all class member
private void Logout()
{
//call method of UserProfileViewModel
App.Locator.UserProfile.ClearProfileData();
//code for logout
}
}
}
User Profile View Model
[ImplementPropertyChanged]
public class UserProfileViewModel : ViewModelBase
{
public UserProfileViewModel(INavigationService nService, CurrentUserContext uContext, INotificationService inService)
{
//initialize all class member
}
//Is there any other way to clear the data rather manually?
public void ClearProfileData()
{
FirstName = LastName = UserName = string.Empty;
}
}
ViewModel Locator
public class ViewModelLocator
{
static ViewModelLocator()
{
MySol.Default.Register<UserProfileViewModel>();
}
public UserProfileViewModel UserProfile => ServiceLocator.Current.GetInstance<UserProfileViewModel>();
}
Firstly there is no need to cleanup these kinds of primitive data types, the gc will do that for you.
However if you use Messages or any other Strong Reference for that matter you WILL have to Unsubscribe from them otherwise your viewmodal will hang around in memory and will never go out of scope
The garbage collector cannot collect an object in use by an
application while the application's code can reach that object. The
application is said to have a strong reference to the object.
With Xamarin it really depends how you are coupling your View to Viewmodals to determine which approach you might take to cleanup your viewmodals.
As it turns out MVVM Light ViewModelBase implements an ICleanup interface which has an overridable Cleanup method for you.
ViewModelBase.Cleanup Method
To cleanup additional resources, override this method, clean up and
then call base.Cleanup().
public virtual void Cleanup()
{
// clean up your subs and stuff here
MessengerInstance.Unregister(this);
}
Now your just left with where to call ViewModelBase.Cleanup
You can just call it when your View Closes, if you get a reference to the DataContext (I.e ViewModalBase) on the DataContextChanged Event
Or you can wire up a BaseView that plumbs this for you, or you can implement your own NagigationService which calls Cleanup on Pop. It really does depend on who is creating your views and viewmodels and how you are coupling them

Change language for the whole application inside a fragment?

Let's say this is the call stack:
Fragment2 (current position)
Fragment1
HostingActivity
The user has just navigated to fragment2 (which is the settings screen). He chooses a second language, and then navigates back to Fragment1. I want the app to show content from the second language as soon as he enters fragment1.
I've read that the best approach would be to let all the fragments derive from a base-fragment which configures the locale inside the OnResume() method.
public class BaseFragment : Fragment
{
public override void OnResume()
{
string langCode = prefs.GetString("settings_language", "en_US");
Resources res = Application.Context.Resources;
// Change locale settings in the app.
DisplayMetrics dm = res.DisplayMetrics;
Configuration conf = res.Configuration;
conf.SetLocale(new Locale(langCode.ToLower()));
res.UpdateConfiguration(conf, dm);
OnConfigurationChanged(conf);
base.OnResume();
}
}
I've attempted this, but without any luck. I didn't see any change whatsoever.
I'm open to hear any suggestions.
PS. To ensure that I didn't mess up any of the naming conventions required by Android in the values folder, I tried rebooting the emulator (with adb shell) using the language I wanted and it worked as expected.
You can create a base class that sets the CurrentCulture like this and inherit from it:
internal class MyBaseActivity : Activity
{
protected override void OnResume ()
{
base.OnResume ();
// Get the language from wherever.
var userSelectedCulture = new CultureInfo ("fr-FR");
Thread.CurrentThread.CurrentCulture = userSelectedCulture;
}
}
And here is how to inherit and use it:
public class MainActivity : MyBaseActivity
{
protected override void OnResume()
{
base.OnResume(); // Do not forget to call base.OnResume()
// Rest of Your Code...
}
}

Xamarin Forms UWP Push Notification Click

I have implemented a push notification feature for my Xamarin.Forms UWP app and i am able receive notifications and then pop up a toast. I am using the code below for this.
//This method is triggered when a user taps/clicks the notification
private void Toast_Activated(ToastNotification sender, object args)
{
ToastActivatedEventArgs targs = (ToastActivatedEventArgs)args;
var xml = sender.Content.GetXml();
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(xml);
XmlNodeList txts = xDoc.GetElementsByTagName("text");
var sitid = txts[0].InnerText;
var id = sitid.Substring(sitid.LastIndexOf(':') + 1);
Debug.WriteLine("Id:" + id);
}
When a user clicks/taps the notification, I want to open a specific page from my PCL project and pass this id variable as an argument. How can i do this ? Any help would be appreciated.
Use ServiceLocator or whatever other dependency injection framework you have to call your navigation service.
If you want to use the Xamarin Forms built in one see here - http://developer.xamarin.com/guides/cross-platform/xamarin-forms/dependency-service/
Basically you then define an interface of
public interface INavigationService
{
void NavigateTo(String pageKey);
}
Then you create a new class of
public class NavigationService: INavigationService
{
private NavigationPage _navPage;
public void Initialize(NavigationPage navPage)
{
_navPage = navPage;
}
public void NavigateTo(String pageKey)
{
// Get Page from pageKey
_navPage.PushAsync(page);
}
}
If you want to see how it is done in MVVMLight you can look here: http://www.mvvmlight.net/doc/nav1.cshtml
You can just use the ServiceLocator or other to get the navigation service as needed, whether its in native code or not.
OR
The other way around is that you Dependency Inject another Service type and load up another class from inside Forms. Then you just pass the action through to that, and it can perform the navigation while you are inside Forms.
I've use this tutorial for your purpose, I hope to help you

Categories