Android image localisation in Xamarin Form - xamarin

I have a Xamarin Form application which has a language dropdown so user can select the application language. When user select a language I call:
CultureInfo culture = CultureInfo.CreateSpecificCulture(language);
System.Globalization.CultureInfo.CurrentUICulture = culture;
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
AppResources.Culture = culture;
The application works fine and picks the strings from AppResources.{language}.resx
My problem is when localising an image. As per Microsoft's suggestion I have added my image to Resources/drawable folders inside the android project. In my case, I added thanks.jpg to Resources/drawable and Resources/drawable-fr. But it only shows the image on Resources/drawable folder even when I select fr-FR as language, however I found out if instead of changing the application language(culture), I change the device language, the device show the correct image(the image inside Resources/drawable-fr). I was wondering if there is any way to fix this issue.

You can create a dependence service to fix it.
First of all, create a interface.
public interface IChangeService
{
void ChangeIanguage(string lang);
}
We used it in the this format code in the xamarin forms.
<StackLayout>
<TimePicker></TimePicker>
<Image WidthRequest="100">
<Image.Source>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="flag.png" />
<On Platform="UWP" Value="Assets/Images/flag.png" />
</OnPlatform>
</Image.Source>
</Image>
<Button Text="Change" Clicked="Button_Clicked"></Button>
</StackLayout>
Here is background code.
public partial class LocalizedXamlPage : ContentPage
{
public LocalizedXamlPage()
{
InitializeComponent();
}
private void Button_Clicked(object sender, System.EventArgs e)
{
DependencyService.Get<IChangeService>().ChangeIanguage("en");
}
}
Then achieve the interface in the android platform. If we change the localization, we need it to work at the runtime, we should restart our application.
[assembly: Dependency(typeof(ChangeLanguageService))]
namespace UsingResxLocalization.Droid
{
public class ChangeLanguageService : IChangeService
{
public void ChangeIanguage(string lang = "in")
{
LanguageManager.ChangeLanguage(MainActivity.instance, lang);
//restart your application.
Intent intent = new Intent(MainActivity.instance, typeof(MainActivity));
intent.SetFlags(ActivityFlags.ClearTask | ActivityFlags.NewTask);
MainActivity.instance.StartActivity(intent);
}
}
}
Then we need to create a BaseActivity(because we change the Locale in android, we should use the same Context.) and LanguageManager(change the localization at the runtime)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Support.V7.App;
using Android.Views;
using Android.Widget;
using Java.Util;
using Xamarin.Forms.Platform.Android;
namespace UsingResxLocalization.Droid
{
public class BaseActivity : FormsAppCompatActivity
{
protected override void AttachBaseContext(Context #base)
{
base.AttachBaseContext(LanguageManager.LoadLanguage(#base));
}
}
public class LanguageManager
{
private const string MYLANGUAGE = "myLanguage";
private const string MYPREF = "myPreference";
public static Context LoadLanguage(Context context)
{
var loadedLanguage = GetLanguage(context, Locale.Default.Language);
return ChangeLanguage(context, loadedLanguage);
}
public static Context ChangeLanguage(Context context, string language)
{
SaveLanguage(context, language);
if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
{
return ChangeForAPI24(context, language);
}
return ChangeForLegacy(context, language);
}
private static string GetLanguage(Context context, string Language)
{
var privatePreference = context.GetSharedPreferences(MYPREF, FileCreationMode.Private);
return privatePreference.GetString(MYLANGUAGE, Language);
}
private static void SaveLanguage(Context context, string language)
{
var privatePreference = context.GetSharedPreferences(MYPREF, FileCreationMode.Private);
var editor = privatePreference.Edit();
editor.PutString(MYLANGUAGE, language);
editor.Apply();
}
private static Context ChangeForAPI24(Context context, string language)
{
// for api >= 24
var locale = new Locale(language);
Locale.Default = locale;
var configuration = context.Resources.Configuration;
configuration.SetLocale(locale);
configuration.SetLayoutDirection(locale);
return context.CreateConfigurationContext(configuration);
}
private static Context ChangeForLegacy(Context context, string language)
{
var locale = new Locale(language);
Locale.Default = locale;
var resources = context.Resources;
var configuration = resources.Configuration;
configuration.Locale = locale;
resources.UpdateConfiguration(configuration, resources.DisplayMetrics);
return context;
}
}
}
To make the mainAcitvity extend the BaseActivity.cs. And expose the public static MainActivity instance;,
[Activity(Label = "UsingResxLocalization", Icon = "#mipmap/icon", Theme = "#style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : BaseActivity
{
public static MainActivity instance;
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
instance = this;
base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
}
Here is runinng GIF.
========Update=========
I make a test with France flag by changing language to fr when the device language is English.
I upload my demo to you, you can test it.
https://github.com/851265601/LocalzationDemo/blob/master/LocalzationDemoWithFlag.zip
If you want to change the text of Button or other label, when you click the Button, Please add CultureInfo.CurrentUICulture = new CultureInfo("fr", false); as well in the click event. You can see this GIF.

Related

Binding text label on a home page to a timer

We have a really simple app, the idea is the timer will update a label on the home screen depending on different configuration within the mobile app. I have created the binding and can update the homepage from it's self but not from the timer. I think what is missing is a OnChange within the home page to detect if the string has changed.
Display layout code, bind the label to the name "LabelText"
<Label
Text = "{Binding LabelText, Mode=TwoWay}"
x:Name="MainPageStatusText"
HorizontalOptions="CenterAndExpand"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="6"
VerticalOptions="CenterAndExpand"
TextColor="White"
FontSize="Medium"/>
This is the class file to link the text string to the label, I can see it been called from the different places but when it's called from the app.cs it does not work
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Xamarin.Forms;
namespace Binding_Demo
{
public class MyClass : INotifyPropertyChanged
{
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{ PropertyChanged?.Invoke(this, e); }
protected void OnPropertyChanged(string propertyName)
{ OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); }
public event PropertyChangedEventHandler PropertyChanged;
private string labelText;
public string LabelText
{
get {
return labelText;
}
set
{
labelText = value;
OnPropertyChanged("LabelText");
}
}
}
}
This is the code inside the homepage, this works and I can see it sending data to the text label
public static MyClass _myClass = new MyClass();
public Homepage()
{
BindingContext = _myClass;
_myClass.LabelText = "Inside the home page";
}
This is the App.cs code, we start the timer and then want to set the text on the Homepage label. I can see the class been called, but it does not set the text.
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Binding_Demo
{
public partial class App : Application
{
public static MyClass _myClass = new MyClass();
public App()
{
//InitializeComponent();
Device.StartTimer(TimeSpan.FromSeconds(10), () =>
{
Task.Run(() =>
{
Debug.WriteLine("Timer has been triggered");
// !!!!! This is not setting the text in the label !!!!!
BindingContext = _myClass;
_myClass.LabelText = "Inside the timer app";
});
return true; //use this to run continuously
});
MainPage = new NavigationPage(new MainPage());
}
protected override void OnStart()
{
//
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
// force app to mainpage and clear the token
}
}
}
I have created the binding and can update the homepage from it's self but not from the timer.
As Jason said, please make sure the binding model is unique. You could create a global static instance of MyClass in App class, then bind this instance to HomePage.
Check the code:
App.xaml.cs
public partial class App : Application
{
public static MyClass _myClass = new MyClass();
public App()
{
InitializeComponent();
Device.StartTimer(TimeSpan.FromSeconds(5), () =>
{
Task.Run(() =>
{
_myClass.LabelText = "Inside the timer app";
});
return true;
});
MainPage = new NavigationPage(new Homepage());
}
}
Homepage.xaml.cs:
public Homepage()
{
InitializeComponent();
BindingContext = App._myClass;
}

create xamarin process never end

I have a plan and want to periodically check a URL every 5 minutes(NOTIFY CENTER SERVER(Listener)).
My problem: Once the program closes, the process closes
Is it possible that the project will not be shut down if the original program is closed ?
My Code After Changed Worked with : Matcha.BackgroundService
using System;
using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Matcha.BackgroundService.Droid;
using Matcha.BackgroundService;
using System.Threading.Tasks;
using Android.Util;
using System.Threading;
using AndroidApp = Android.App.Application;
using Android.Content;
using Android.Support.V4.App;
using Android.Graphics;
namespace Solution.Droid
{
[Activity(Label = "Solution", Icon = "#mipmap/icon", Theme = "#style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
private NotificationManager _manager;
private bool _channelInitialized = false;
public const int _pendingIntentId = 0;
public int _channelID = 10001;
private long _mssageID=0;
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
BackgroundAggregator.Init(this);
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
protected override void OnStart()
{
base.OnStart();
//Register Periodic Tasks
var _notifyTASK = new DevinuxTaskPeriodic(10);
_notifyTASK.DoTask += () =>
{
SendNotify("salam", DateTime.Now.ToString());
};
BackgroundAggregatorService.Add(() => _notifyTASK);
BackgroundAggregatorService.StartBackgroundService();
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
public int SendNotify(string title, string message)
{
_mssageID++;
if (!_channelInitialized)
{
CreateNotificationChannel();
}
Intent intent = new Intent(AndroidApp.Context, typeof(MainActivity));
PendingIntent pendingIntent = PendingIntent.GetActivity(AndroidApp.Context, _pendingIntentId, intent, PendingIntentFlags.OneShot);
NotificationCompat.Builder builder = new NotificationCompat.Builder(AndroidApp.Context, _channelID.ToString())
.SetContentIntent(pendingIntent)
.SetContentTitle(title)
.SetContentText(message)
.SetLargeIcon(BitmapFactory.DecodeResource(AndroidApp.Context.Resources, Resource.Drawable.notification_template_icon_bg))
.SetSmallIcon(Resource.Drawable.notification_template_icon_bg)
.SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate);
Notification notification = builder.Build();
_manager.Notify((int)_mssageID, notification);
return (int)_mssageID;
}
void CreateNotificationChannel()
{
_manager = (NotificationManager)AndroidApp.Context.GetSystemService(AndroidApp.NotificationService);
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
var channelNameJava = new Java.Lang.String("Solution");
var channel = new NotificationChannel(_channelID.ToString(), channelNameJava, NotificationImportance.Default)
{
Description = "My Company Notify Camp."
};
_manager.CreateNotificationChannel(channel);
}
_channelInitialized = true;
}
public class DevinuxTaskPeriodic : IPeriodicTask
{
public bool use { set; get; } = false;
public delegate void DoArgs();
public event DoArgs DoTask;
public DevinuxTaskPeriodic(int seconds)
{
Interval = TimeSpan.FromSeconds(seconds);
}
public TimeSpan Interval { get; set; }
public Task<bool> StartJob()
{
if (!use)
{
Timer tmr = new Timer((o) => {
if (DoTask != null)
{
DoTask();
}
}, null, 0, (int)Interval.TotalSeconds*1000);
}
use = true;
return new Task<bool>(() => true);
}
}
}
}
Yes, it is possible to run processes even when the original program/app is not in the foreground.
You are entering the territory of "backgrounding" which is less simple to do. There isn't an inbuilt/official way of performing backgrounding using Xamarin.Forms, so you will have to either create a dependency service (shown here), or try using Shiny.
If you follow the dependency services route, you just need to follow the official iOS & Android tutorials and implement them in your Native project. Note that if you only need a periodic alarm, Android provides a simpler Alarm/PowerManager that you can use.

Why when I try to set ActionBar.CustomView.SetBackgroundColor (Color.White); Does the application crash?

Why when I try to set ActionBar.CustomView.SetBackgroundColor (Color.White); Does the application crash?
I tried installing the ToolbarItems background via style, toolbar.axml via ResourceDictionary but not one of these methods works for me - I think because I'm too dumb.
Now this option is the easiest for me, since there is only one line of code, but somewhere you can see the conflict comes from style or toolbar.axml or something else.
ActionBar.CustomView.SetBackgroundColor (Color.White);
my app.xaml.cs
using System;
using System.IO;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using MyApp1.Services;
using MyApp1.Views;
namespace MyApp1
{
public partial class App : Application
{
static Data.TodoItemDatabase database;
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
protected override void OnStart()
{
// Handle when your app start
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
public static Data.TodoItemDatabase Database
{
get
{
if (database == null)
{
database = new Data.TodoItemDatabase(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TodoSQLite.db3"));
}
return database;
}
}
public int ResumeAtTodoId { get; set; }
}
}
Solution 1:
You can set the style of TitleView in Forms . You can create a base Navigation Pageand set the style of TitleView
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal" BackgroundColor="White" >
//...
</StackLayout>
</NavigationPage.TitleView>
Solution 2
You can try creating a Custom Renderer and set it as the base class for all pages in the app. Within this custom renderer you can try to set the background color and other style as you want in CustomTitleView .
[assembly: ExportRenderer(typeof(Page), typeof(AndroidNavigationPageRenderer )]
public class AndroidNavigationPageRenderer : PageRenderer
{
private CustomTitleView _titleView;
public NavigationSearchRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
var activity = this.Context as FormsAppCompatActivity;
if (activity == null)
return;
var toolbar = activity.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
_titleView= new TitleView(Context);
toolbar.AddView(_titleView);
}
}

passing string data from Android MainActivity to PCL TabbedPage xamarin.form

Hello everyone Good Day how do I pass string value or data from Android MainActivity to PCL TabbedPage?
MainActivity.cs
using System;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Runtime;
using Android.OS;
using Android.Telephony;
using Xamarin.Forms;
namespace NegDrClient.Droid
{
[Activity (Label = "Neg Client", Icon = "#drawable/logo", MainLauncher = true, ScreenOrientation = ScreenOrientation.Portrait)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
global::Xamarin.Forms.Forms.Init (this, bundle);
global::ZXing.Net.Mobile.Forms.Android.Platform.Init ();
LoadApplication (new App ());
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
global::ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult (requestCode, permissions, grantResults);
}
public string GetNumber()
{
TelephonyManager telephonyManager =
(TelephonyManager) Forms.Context.GetSystemService(Android.Content.Context.TelecomService);
var number = telephonyManager.Line1Number;
return number.ToString();
}
}
}
I need to call the GetNumber() method so I can assign it to my login entry, Thanks.
You can do this via a Dependency Service.
Add the following interface to your Xamarin.Forms project:
public interface IPhone
{
string GetNumber();
}
Add that interface to your existing MainActivity within your Xamarin.Android project:
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity, IPhone
Register that Dependency class (above your namespace NegDrClient.Droid):
[assembly: Xamarin.Forms.Dependency(typeof(MainActivity))]
Now you can call it in your Xamarin.Forms project:
var GetNumberButton = new Button()
{
Command = new Command(() => {
Device.BeginInvokeOnMainThread(() =>
{
var number = DependencyService.Get<IPhone>().GetNumber();
System.Diagnostics.Debug.WriteLine(number);
});
})
};
Note: Your GetNumber method has an error in it, should be:
public string GetNumber()
{
var telephonyManager = (TelephonyManager)Forms.Context.GetSystemService(Context.TelephonyService);
return telephonyManager.Line1Number;
}

How to load PDF in Xamarin Forms

I have a Xamarin Forms app where I want to open a locally stored PDF. I don't need to load them within the app, I'm fine with shelling out to the device's default document viewer for PDFs. How can I do this?
I tried sending a WebView to the PDF, but that didn't work, I just got a blank page.
I've recently done this in my own project using a custom renderer. First implement an empty Xamarin forms view such as (I've included a bindable FilePath attribute):
public class PdfViewer : View
{
public static readonly BindableProperty FilePathProperty =
BindableProperty.Create<DocumentViewer, string>(p => p.FilePath, null);
public string FilePath
{
get
{
return (string)this.GetValue(FilePathProperty);
}
set
{
this.SetValue(FilePathProperty, value);
}
}
}
Then create an iOS Renderer that will be registered for this control. This renderer can, as it is within an iOS project, use the Quick Look Preview Controller to delegate to the built in iOS pdf viewer:
[assembly: ExportRenderer(typeof(PdfViewer), typeof(DocumentViewRenderer))]
public class DocumentViewRenderer
: ViewRenderer<PdfViewer, UIView>
{
private QLPreviewController controller;
protected override void OnElementChanged(ElementChangedEventArgs<DocumentViewer> e)
{
base.OnElementChanged(e);
this.controller = new QLPreviewController();
this.controller.DataSource = new DocumentQLPreviewControllerDataSource(e.NewElement.FilePath);
SetNativeControl(this.controller.View);
}
private class DocumentQLPreviewControllerDataSource : QLPreviewControllerDataSource
{
private string fileName;
public DocumentQLPreviewControllerDataSource(string fileName)
{
this.fileName = fileName;
}
public override int PreviewItemCount(QLPreviewController controller)
{
return 1;
}
public override QLPreviewItem GetPreviewItem(QLPreviewController controller, int index)
{
var documents = NSBundle.MainBundle.BundlePath;
var library = Path.Combine(documents, this.fileName);
NSUrl url = NSUrl.FromFilename(library);
return new QlItem(string.Empty, url);
}
private class QlItem : QLPreviewItem
{
public QlItem(string title, NSUrl uri)
{
this.ItemTitle = title;
this.ItemUrl = uri;
}
public override string ItemTitle { get; private set; }
public override NSUrl ItemUrl { get; private set; }
}
}
}
I haven't compiled and run this as I've extracted it from my larger project but in general this should work.
I had to do something and solve it using a DependencyService . You can use it to open the pdf depending on each platform
I show you an example of how to solve it on Android :
IPdfCreator.cs:
public interface IPdfCreator
{
void ShowPdfFile();
}
MainPage.cs:
private void Button_OnClicked(object sender, EventArgs e)
{
DependencyService.Get<IPdfCreator>().ShowPdfFile();
}
PdfCreatorAndroid.cs
[assembly: Dependency(typeof(PdfCreatorAndroid))]
namespace Example.Droid.DependecyServices
{
public class PdfCreatorAndroid : IPdfCreator
{
public void ShowPdfFile()
{
var fileLocation = "/sdcard/Template.pdf";
var file = new File(fileLocation);
if (!file.Exists())
return;
var intent = DisplayPdf(file);
Forms.Context.StartActivity(intent);
}
public Intent DisplayPdf(File file)
{
var intent = new Intent(Intent.ActionView);
var filepath = Uri.FromFile(file);
intent.SetDataAndType(filepath, "application/pdf");
intent.SetFlags(ActivityFlags.ClearTop);
return intent;
}
}
}
Result:
http://i.stack.imgur.com/vrwzt.png
Here there is a good project that uses MuPDF Library in xamarin . I've tested it and it works properly.
With MuPDF you can zoom out , zoom in and even write some note on PDFs.

Resources