Change language for the whole application inside a fragment? - c#

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...
}
}

Related

How to create a Spec-Flow test for a Prism 7 app?

I'm in the process of creating a new windows desktop app with Prism 7, and I started out with the new PrismApplication base class that replaced the bootstrapper. Everything was fine until I created the (specflow-)tests.
I was used to re-using the original bootstrapper during initialization of the test-app, just modifying the registrations afterwards. That, transformed to the new Application derived system looks like this:
internal partial class App
{
protected override IContainerExtension CreateContainerExtension()
{
var containerExtension = new Prism.Unity.Ioc.UnityContainerExtension();
containerExtension.Instance.AddExtension( new LogExtension() );
return containerExtension;
}
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes( IContainerRegistry containerRegistry )
{
containerRegistry.Register<IConfiguration, AppSettingsConfiguration>();
containerRegistry.Register<IWindowsInterface, WindowsInterface>();
// ... a lot of registrations removed here ...
}
}
And a derived test-app that does everything but create the shell:
private class MyApp : App
{
protected override Window CreateShell()
{
return null;
}
}
Wrapped up in a BeforeScenario hook to initialize the test-app:
[BeforeScenario]
public void InitializeApp()
{
var app = new MyApp();
app.Initialize();
var containerRegistry = (IContainerRegistry)app.Container;
containerRegistry.RegisterSingleton<TestWindowsInterface>();
containerRegistry.Register<IWindowsInterface,TestWindowsInterface>();
// ... some registration overrides removed here ...
_objectContainer.RegisterInstanceAs<App>( app );
}
And a step to create the main window (CreateShell replacement):
[When( #"I start the software" )]
public void WhenIStartTheSoftware()
{
_container.RegisterInstanceAs( _container.Resolve<App>().Container.Resolve<MainWindowViewModel>() );
}
So far, so good, this works. But only as long as you have just one scenario. As soon as the second scenario starts, we get an exception:
Cannot create more than one System.Windows.Application instance in the same AppDomain.
In the old days, this wasn't a problem, because the Bootstrapper was just a regular class, as opposed to the PrismApplication which is enforced to be a singleton by the framework.
Of course, I can move the whole registration stuff into a regular class, and use that to initialize the test-app, but this means essentially creating my own version of the bootstrapper on top of the PrismApplication. Using the classic Bootstrapper makes more sense to me, yet it will be dropped in a future release (as it's marked obsolete today).
You will be able to create several instance of the Application class using a class that derives from MarshalByRefObject:
public class AppDomainWrapper : MarshalByRefObject
{
public void DoSomething()
{
var app = new MyApp();
app.Initialize();
...
app.Shutdown();
}
}
Sample usage:
AppDomain appDomain = AppDomain.CreateDomain("AppDomain");
AppDomainWrapper app = appDomain.CreateInstanceAndUnwrap(typeof(AppDomainWrapper).Assembly.FullName, typeof(AppDomainWrapper).FullName) as AppDomainWrapper;
app.DoSomething();
AppDomain.Unload(appDomain);

Android DataBinding - How to Bind A ViewGroup's Properties

I am developing an Android app in Xamarin.Android(C#). However, i do feel that this question can also be answered by any Java devs as well.
I am new in android development. Anyways, i created a fragment with just a LinearLayout and a TextView inside it. When i create the background class for it, i don't inherit(on in JAVA's word, extend) it from the Fragment class but rather from the LinearLayout class.
So, the MyFragment.cs file starts like this :
public class MyFragment : LinearLayout
The JAVA equivalent would be
public class MyFragment extends LinearLayout
(P.S. I have limited knowledge of JAVA and it's sytaxes).
Anyways, all works fine. I have an Initialize method(In JAVA, it should be the Init method) which inflates the view of the fragment. From the view, it tries to find the TextView with the given Id.
So, the codes looks like this :
public class MyFragment : LinearLayout
{
Context mContext;
private void Initialize(Context ctx)
{
//Inflating the layout
mContext = ctx;
var inflatorService = (LayoutInflater)ctx.GetSystemService(Context.LayoutInflaterService);
View v = inflatorService.Inflate(Resource.Layout.MyFragmentView, this, false);
this.AddView(v);
GoalHeader = v.FindViewById<TextView>(Resource.Id.GoalHeader);
}
All works pretty well this far. I then go on implementing the MVVM pattern, using MVVMLight library. I create a ViewModel as follows :
public class Vm_MyFragment : ViewModelBase
{
private string _goaltitle = "";
public string GoalTitle
{
get { return _goaltitle; }
set { Set(ref _goaltitle, value); }
}
public void SetTest()
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
GoalTitle = "Test";
});
}
}
Still, everything's good. The problem starts when i try to bind the TextView's text property to the ViewModel's GoalTitle property, as follows :
private readonly List<Binding> _bindings = new List<Binding>();
private void Initialize(Context ctx)
{
//Inflating the layout
mContext = ctx;
var inflatorService = (LayoutInflater)ctx.GetSystemService(Context.LayoutInflaterService);
View v = inflatorService.Inflate(Resource.Layout.MyFragmentView, this, false);
this.AddView(v);
Vm_MyFragmentView viewmodel = new Vm_MyFragmentView();
GoalHeader = v.FindViewById<TextView>(Resource.Id.GoalHeader);
_bindings.Add(
this.SetBinding(
() => mainViewModel.GoalTitle,
() => GoalHeader.Text));
}
Note : Binding is from the GalaSoft.MvvmLight.Helpers namespace.
I add the fragment in my main view(I mean, MainActivity's view) and debug the app. Upon execution, i get the following error :
Could not activate JNI Handle 0xfff02a68 (key_handle 0x339790a) of Java type 'md55bfae9a06327fa0fdf207b4f768604b1/MyFragment' as managed type 'TestApp.MyFragment'.
Searching google, i realized that i am trying to bind the property before the view is even created(correct me if i'm wrong). The suggestions i found on other SO answers were either to put the code in the OnCreateView method or somehow delay the execution of the binding part's code.
The first solution didn't work for me as LinearLayout aka a View doesn't have such a method OnCreateView which i can override.
So, how am i supposed to bind the TextView to the ViewModel then? And also, am i on the right track on treating the fragment as a LinearLayout as i am inheriting from it?
Im not familiar with MVVMLIght extension but if you are using a fragment as it is supposed to (ie. in a tablayout) you should inherit from a fragment like this (This is a v4 support fragment):
public class CategoryFragment : SupportFragment {
RecyclerView _recyclerView;
private View _view;
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
_view = inflater.Inflate (Resource.Layout._CategoryLayout, container, false);
// Get our RecyclerView layout:
_recyclerView = _view.FindViewById<RecyclerView> (Resource.Id.categoryRecyclerView);
// Instantiate the layout manager
var linearLayoutManager = new LinearLayoutManager (Context, LinearLayoutManager.Vertical, false);
_recyclerView.SetLayoutManager (linearLayoutManager);
// Instantiate the adapter and pass in its data source:
_adapter = new CategoryAdapter (_categories);
//Register the item click handler with the adapter:
_adapter.ItemClick += OnItemClick;
// Plug the adapter into the RecyclerView:
_recyclerView.SetAdapter (_adapter);
return _view;
}
}

How can i display image stored in mobile device on my aplication?

I have an aplication where at some point i need to acess user's contacts list and get data from there. For this i used Xamarin.Forms.Contacts(1.0.5) plugins and it worked well. I was able to get Name Number Email PhotoUri PhotoUriThumbnail from each contact. And then i display some of the infos on my aplication. However, i am not able to display image from PhotoUri directory. PhotoUri is a string with this format : content://android/.... I tried converting PhotoUri to ImageSource and then use it on xaml file but nothing worked... Can anyone help ?
For getting data from a content:// URI, you can use the ContentResolver. Specifically you can load the contents from the file with ContentResolver.OpenInputStream (see here). To display the image you could use a StreamImageSource (see here). Given you already have an Uri, you can instantiate the StreamImageSource as seen in the following snippet
var contentResolver = Application.ApplicationContext.ContentResolver;
var streamImageSource = new StreamImageSource()
{
Stream = (cancellationToken) => Task.FromResult(contentResolver.OpenInputStream(uri));
}
Please note: If PhotoUri is note derived from Android.Net.Uri you'll have to convert it to the latter.
Edit
The code presented works from MainActivity, only. As an workaround I've added an static property Instance to MainActivity that is assigned in OnCreate
public static MainActivity Instance { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
this.Window.RequestFeature(WindowFeatures.ActionBar);
this.SetTheme(Resource.Style.MainTheme);
base.OnCreate(savedInstanceState);
MainActivity.Instance = this;
// whatever
}
you can then use
var contentResolver = MainActivity.Instance.Application.ApplicationContext.ContentResolver;
which might not be optimal, but works. Alternatively (which I'd prefer) you could inject the MainActivity to your instances.
Edit 2
Since the question arose how to use this code from Xamarin.Forms, I'll give a short outline. If you're not using dependency injection, the easiest way will be using DependencyService (see here). Create an interface in your shared code
public interface IContentLoader
{
ImageSource LoadFromContentUri(Uri uri);
}
The implementation of this interface has to be added to the platform project
[assembly: Dependency (typeof (Droid.ContentLoader))]
namespace Droid
{
public class ContentLoader : IContentLoader
{
public ImageSource LoadFromContentUri(Uri uri)
{
var contentResolver = MainActivity.Instance.Application.ApplicationContext.ContentResolver;
var streamImageSource = new StreamImageSource()
{
Stream = (cancellationToken) => Task.FromResult(contentResolver.OpenInputStream(Android.Net.Uri.Parse(uri.ToString())));
}
return streamImageSource;
}
}
}
Now the IContentLoader can be used from your Xamarin.Forms project using the DependencyService:
var contentLoader = DependencyService.Get<IContentLoader>();
// ...
var imageSource = contentLoader.LoadFromContentUri(uri);
Please note: If you are programming for iOS and Android, you'll have to take care that you can load your images from both platforms.

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;
}
}

Alert Dialog in ViewModel - MVVMCross

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

Categories