I have a view with an image: <Image Source="{Binding MainUrl}"/>
Image binding value : My image source on loading from database
the value of MainUrl variable is stored in an sqlite table.public string LogoUrl { get; set; }
viewmodel :
private string _logoUrl;
public string LogoUrl
{
get { return _logoUrl; }
set
{
SetValue(ref _logoUrl, value);
OnPropertyChanged(nameof(LogoUrl));
}
}
the user has the option to change the image using filepicker :
var file = await FilePicker.PickAsync(options);
if (file == null)
{
return;
}
var stream = await file.OpenReadAsync();
MainUrl = ImageSource.FromStream(() => stream);
LblImportLogo = file.FullPath;
Parameter.LogoUrl = file.FullPath;
when I choose an image with filepicker, this one is well displayed and the the path is saved into sqlite table but when I restart the application, the image is no longer displayed despite the path being well informed (this one is external). the problem is in all platforms, UWP,android and IOS. can you help me find a solution.
Mainpage.xaml.cs :
public MainPage()
{
var parameterStore = new SQLiteParameterStore(DependencyService.Get<ISQLiteDb>());
var pageService = new PageService();
ViewModel = new MainPageViewModel(parameterStore, pageService);
InitializeComponent();
NavigationPage.SetHasBackButton(this, false);
NavigationPage.SetHasNavigationBar(this, false);
}
protected override void OnAppearing()
{
ViewModel.LoadDataCommand.Execute(null);
base.OnAppearing();
}
public MainPageViewModel ViewModel
{
get { return BindingContext as MainPageViewModel; }
set { BindingContext = value; }
}
MainPageViewModel :
......
private ImageSource _mainurl;
public ImageSource MainUrl
{
get { return _mainurl; }
set
{
SetValue(ref _mainurl, value);
OnPropertyChanged(nameof(MainUrl));
}
}
......
private async Task LoadData()
{
var parameters = await _parameterStore.GetAll();
var elts = parameters.Count();
if (elts < 1)
{
//set default values
mainParameter = new Parameter();
mainParameter.LogoUrl = "default.jpg";
MainUrl = mainParameter.LogoUrl;
}
else
{
//set parameters
mainParameter = parameters[0];
MainUrl = ImageSource.FromFile(mainParameter.LogoUrl);
}
}
Related
I am making an application in Xamarin Forms and I have a Login when loading the application. The case is that when I put the data, I make a call to an API and it returns a series of data in a json.
Then I go through the json, checking that each data exists in my local database (sqlite), if it exists, I update it, if it does not exist, I insert it.
Once it has gone through all the json it loads the following activity, or screen ...
The problem is that it easily takes 10 to 15 seconds to move on to the next activity.
Any idea how to optimize this process? Or, should I modify the API to get less data?
CODE:
Login.xaml.cs
namespace WorkersApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Login : ContentPage
{
public Login()
{
InitializeComponent();
}
public string user = "";
public string password = "";
private async void btnEncript_Clicked(object sender, EventArgs e)
{
if (userEntry.Text != null && passwordEntry.Text != null)
{
user = userEntry.Text;
password = EncriptarMD5.MD5Hash(passwordEntry.Text);
string url = "https://api.com/?f=login&u=" + user + "&pw=" + password;
await GetDataFromApi(url);
}
}
public async Task GetDataFromApi(string url)
{
HttpClient myClient = new HttpClient();
try
{
var response = await myClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
//ERRORES
var error = JsonConvert.DeserializeObject<Errores>(content);
List<string> errror = new List<string>();
foreach (var item in error.error)
{
errror.Add(item.Value);
}
//Tiro error.
if (errror[0] != "0")
{
Preferences.Set("IdError", errror[0]);
Preferences.Set("Nombre", errror[1]);
return;
}
else
{
Preferences.Set("IdError", errror[0]);
Preferences.Set("Nombre", errror[1]);
}
//HOTELES
var hoteles = JsonConvert.DeserializeObject<Hoteles>(content);
List<Model.Hoteles> lista_hoteles = await App.Database.GetHotelesAsync();
List<Model.Bloques> lista_bloques = await App.Database.GetBloquesAsync();
foreach (var item in hoteles.hoteles)
{
Model.Hoteles h = new Model.Hoteles();
h = JsonConvert.DeserializeObject<Model.Hoteles>(item.Value.ToString());
h.ID = item.Key.ToString();
var bloques = JsonConvert.DeserializeObject<Bloques>(item.Value.ToString());
bool existe = false;
foreach (var bloque in bloques.bloques)
{
existe = false;
Model.Bloques b = new Model.Bloques();
b.ID = bloque.Key.ToString();
b.Nombre = bloque.Value.ToString();
b.IDHotel = item.Key.ToString();
foreach (var iLista in lista_bloques)
{
if (b.ID == iLista.ID)
{
existe = true;
}
}
if (existe)
{
await App.Database.UpdateBloquesAsync(b);
}
else
{
await App.Database.SaveBloquesAsync(b);
}
}
existe = false;
//if exists
foreach (var iLista in lista_hoteles)
{
if (h.ID == iLista.ID)
{
existe = true;
}
}
if (existe)
{
await App.Database.UpdateHotelesAsync(h);
}
else
{
await App.Database.SaveHotelesAsync(h);
}
}
//SECCIONES
var secciones = JsonConvert.DeserializeObject<Secciones>(content);
List<Model.Seccion> lista_secciones = await App.Database.GetSeccionesAsync();
foreach (var item in secciones.secciones)
{
Model.Seccion s = new Model.Seccion
{
ID = item.Key.ToString(),
Nombre = item.Value
};
bool existe = false;
//if exists
foreach (var iLista in lista_secciones)
{
if (s.ID == iLista.ID)
{
existe = true;
}
}
if (existe)
{
await App.Database.UpdateSeccionAsync(s);
}
else{
await App.Database.SaveSeccionAsync(s);
}
}
string data = Preferences.Get("IdError", "");
if (data == "0")
{
Application.Current.MainPage = new HomePage();
}
else
{
await DisplayAlert("Error", Preferences.Get("Nombre", ""), "Cerrar");
}
}
}
catch (Exception exc)
{
Console.WriteLine("EXCEPTION:::: " + exc);
await DisplayAlert("Error", exc.ToString(), "Cerrar");
}
}
}
}
HomePage.xaml.cs
namespace WorkersApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : Shell
{
public IList<Seccion> Secciones { get; set; }
public HomePage()
{
InitializeComponent();
Task.Run(async () =>
Secciones = await App.Database.GetSeccionesAsync()).Wait();
foreach(var item in Secciones)
{
Console.WriteLine(item.Nombre);
switch (item.Nombre)
{
case "Partes":
FlyPartes.IsVisible = true;
//Meter Partes en Parte
string url = "https://api.com/?f=partes.getList";
Task task = GetPartesFromApi(url);
break;
case "Auditorias":
FlyAuditorias.IsVisible = true;
break;
case "Maquinas":
FlyMaquinaria.IsVisible = true;
break;
case "Pisos":
FlyPisos.IsVisible = true;
break;
}
}
}
public async Task GetPartesFromApi(string url)
{
HttpClient myClient = new HttpClient();
try
{
var response = await myClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var error = JsonConvert.DeserializeObject<Errores>(content);
List<string> errror = new List<string>();
foreach (var item in error.error)
{
errror.Add(item.Value);
}
if (errror[0] != "0")
{
Preferences.Set("IdError", errror[0]);
Preferences.Set("Nombre", errror[1]);
return;
}
else
{
Preferences.Set("IdError", errror[0]);
Preferences.Set("Nombre", errror[1]);
}
var partes = JsonConvert.DeserializeObject<Objetos.Partes>(content);
List<Model.Partes> lista = await App.Database.GetPartesAsync();
foreach (var item in partes.partes)
{
Model.Partes p = new Model.Partes();
p = JsonConvert.DeserializeObject<Model.Partes>(item.Value.ToString());
p.ID = item.Key.ToString();
bool existe = false;
//if exists
foreach (var iLista in lista)
{
if (p.ID == iLista.ID)
{
existe = true;
}
}
if (existe)
{
await App.Database.UpdatePartesAsync(p);
}
else
{
await App.Database.SavePartesAsync(p);
}
}
}
}
catch (Exception exc)
{
Console.WriteLine("EXCEPTION:::: " + exc);
await DisplayAlert("Error", exc.ToString(), "Cerrar");
}
}
}
}
Partes.xaml.cs
namespace WorkersApp.Pageviews
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Partes : ContentPage
{
public IList<Model.Partes> Partess { get; set; }
public Partes()
{
InitializeComponent();
Task.Run(async () =>
Partess = await App.Database.GetPartesAsync()).Wait();
int contador = 0;
foreach (var item in Partess)
{
Color c = Color.Black;
if (contador % 2 == 0)
{
c = Color.FromRgb(64, 64, 64);
}
var partesBoxClick = new BoxView()
{
WidthRequest = 900,
HeightRequest = 120,
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start,
BackgroundColor = c,
ClassId = item.ID
};
var partesBoxClick_tap = new TapGestureRecognizer();
partesBoxClick_tap.Tapped += (s, e) =>
{
//OPEN PARTE
GetDatosParteFromApi("https://api.com/?f=partes.get&id= " + partesBoxClick.ClassId, partesBoxClick.ClassId);
};
partesBoxClick.GestureRecognizers.Add(partesBoxClick_tap);
PartesListaView.Children.Add(partesBoxClick, 0, contador);
Color color = Color.Red;
Console.WriteLine("COLOR:::: " + item.Operacion);
switch (item.Operacion)
{
case "nuevo":
color = Color.Red;
break;
case "asignado":
color = Color.GreenYellow;
break;
case "en progreso":
color = Color.Orange;
break;
case "anulado":
color = Color.Gray;
break;
case "en ejecucion":
color = Color.DeepSkyBlue;
break;
case "cerrado":
color = Color.Green;
break;
case "terceros":
color = Color.Firebrick;
break;
}
PartesListaView.Children.Add(new BoxView() { BackgroundColor = color, WidthRequest = 5, HeightRequest = 120, HorizontalOptions = LayoutOptions.Start, VerticalOptions = LayoutOptions.Start }, 0, contador);
PartesListaView.Children.Add(new Label { Text = item.Titulo, FontSize=16, FontAttributes=FontAttributes.Bold, Padding = new Thickness(10, 0, 0, 0), HorizontalOptions = LayoutOptions.Start, VerticalOptions = LayoutOptions.Start }, 0, contador);
PartesListaView.Children.Add(new Label { Text = item.Departamento, Padding = new Thickness(10, 0, 10, 18), HorizontalOptions = LayoutOptions.Start, VerticalOptions = LayoutOptions.End }, 0, contador);
PartesListaView.Children.Add(new Label { Text = item.Hotel, Padding = new Thickness(10, 0, 10, 0), HorizontalOptions = LayoutOptions.Start, VerticalOptions = LayoutOptions.End }, 0, contador);
PartesListaView.Children.Add(new Label { Text = item.Fecha, Padding = new Thickness(10, 0, 10, 18), HorizontalOptions = LayoutOptions.End, VerticalOptions = LayoutOptions.End }, 0, contador);
PartesListaView.Children.Add(new Label { Text = item.Prioridad, Padding = new Thickness(10, 0, 10, 0), HorizontalOptions = LayoutOptions.End, VerticalOptions = LayoutOptions.End }, 0, contador);
contador++;
}
}
public async Task GetDatosParteFromApi(string url, string id)
{
HttpClient myClient = new HttpClient();
try
{
var response = await myClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
//GET ERROR
var error = JsonConvert.DeserializeObject<Errores>(content);
List<string> errror = new List<string>();
foreach (var item in error.error)
{
errror.Add(item.Value);
}
if (errror[0] != "0")
{
Preferences.Set("IdError", errror[0]);
Preferences.Set("Nombre", errror[1]);
return;
}
//GET DATOS
List<string> ids = new List<string>();
List<string> idsh = new List<string>();
var Datos = JsonConvert.DeserializeObject<Parte>(content);
string parte_datos = "";
string parte_fotos = "";
string parte_historico = "";
foreach (var item in Datos.parte)
{
if (item.Key.ToString() == "datos")
{
List<Model.Parte> datosParte = await App.Database.GetDatosParteAsync();
parte_datos = item.Value.ToString();
Model.Parte parte = new Model.Parte();
parte = JsonConvert.DeserializeObject<Model.Parte>(parte_datos);
parte.ID_Parte = id;
bool existe = false;
foreach (var iLista in datosParte)
{
if (parte.ID_Parte == iLista.ID_Parte)
{
existe = true;
}
}
if (existe)
{
await App.Database.UpdateDatosParteAsync(parte);
}
else
{
await App.Database.SaveDatosParteAsync(parte);
}
}
else if (item.Key.ToString() == "fotos")
{
parte_fotos = item.Value.ToString();
List<Model.Fotos> fotos = await App.Database.GetFotosAsync();
List<Model.Fotos> FotoList = new List<Model.Fotos>();
//foto = JsonConvert.DeserializeObject<Model.Fotos>(parte_fotos);
FotoList = JsonConvert.DeserializeObject<List<Model.Fotos>>(parte_fotos);
foreach(var fotoI in FotoList)
{
Model.Fotos foto = new Model.Fotos();
foto = fotoI;
foto.ID_Parte = id;
bool existe = false;
ids.Add(foto.ID_Foto);
foreach (var iLista in fotos)
{
if (foto.ID_Foto == iLista.ID_Foto)
{
existe = true;
}
}
if (existe)
{
await App.Database.UpdateFotoAsync(foto);
}
else
{
await App.Database.SaveFotoAsync(foto);
}
}
}
else if (item.Key.ToString() == "historico")
{
parte_historico = item.Value.ToString();
List<Model.Historico> historicos = await App.Database.GetHistoricoAsync();
List<Model.Historico> HistoricoList = new List<Model.Historico>();
HistoricoList = JsonConvert.DeserializeObject<List<Model.Historico>>(parte_historico);
foreach (var historicoI in HistoricoList)
{
Model.Historico historico = new Model.Historico();
historico = historicoI;
historico.idHistorico = id + historico.fechaEvento;
bool existe = false;
idsh.Add(historico.idHistorico);
foreach (var iLista in historicos)
{
if (historico.idHistorico == iLista.idHistorico)
{
existe = true;
}
}
if (existe)
{
await App.Database.UpdateHistoricoAsync(historico);
}
else
{
await App.Database.SaveHistoricoAsync(historico);
}
}
}
}
string data = Preferences.Get("IdError", "");
if (data == "0")
{
await Navigation.PushAsync(new DatosParte(id, ids, idsh));
}
else
{
await DisplayAlert("Error", Preferences.Get("Nombre", ""), "Cerrar");
}
}
}
catch (Exception exc)
{
Console.WriteLine("EXCEPTION:::: " + exc);
await DisplayAlert("Error", exc.ToString(), "Cerrar");
}
}
private void Filter_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new FiltroPartes());
}
}
}
Thanks for reading.
EDIT:::::
After cleaning the API, now i get fewer Tickes to check if update or save indo local sqlite.
But I just notice I still need to check the whole BBDD to login.
The API now check tickets from last 30 days from curdate and with
operation_state <> "Closed"
I think I'm login (1 call to API to check if user is good)
Then in the HomePage im getting all the tickets (1 call to API to get tickets)
AND THEN. When I click in the Tickets Menu, it loads into the listview. (So another 15 seconds).
I believe i need to remove the API call in HomePage. and add it when I click in Ticket Menu.
Will check in some time. Or tomorrow.
EDIT:
So i checked and i was right, I changed the app to get all the tickets when I tap the Tickets menu.
Thanks for reading.
Cristian, it's normal in mobile app development. According to the amount of data we are getting it takes time to load.
Though as you have mentioned that you are calling service and inserting data if not exist and update if exist, so this data are dynamic and getting changed every time when you call? If not than instead of updating or calling service again, use the data from local database.
If not than You can use monkey cache, that will help you to improve your app performance. But use it only in case of static data.
Another thing instead of getting all data at once from service, try to get only the data that is required and will come in your use, this will also improve your app's performance. And also decrease the amount of internet data it uses.
Refer this for more details: https://heartbeat.fritz.ai/techniques-for-improving-performance-in-a-xamarin-forms-application-b439f2f04156
Also you can remove the images from app and use FontAwesome icons for that.
Hope this will help you in some or other way!
I want to save a string from a TextArea to the device and then reload it after reopening the app. I have tried following the examples (link) but cant get it to work. Main problem arises when i try to read the file and use a StringInputConverter.
private void saveAndLoad(TextArea textArea){
File textFile = new File(ROOT_DIR,"text_file");
String text2 = textArea.getText();
String loadedFile = "none";
if (textFile.exists()){
FileClient fileClient = FileClient.create(textFile);
loadedFile = DataProvider.retrieveObject(fileClient.createObjectDataReader(
new StringInputConverter()));
}
try(FileWriter writer = new FileWriter(textFile)){
writer.write(textArea.getText());
} catch (IOException e) {
e.printStackTrace();
}
textArea.setText(text2);
}
Edit: inserted code which i tried to start reading file with and image of the error i am getting
If you check the DataProvider::retrieveObject documentation:
Retrieves an object using the specified ObjectDataReader. A GluonObservableObject is returned, that will contain the object when the read operation completed successfully.
It returns GluonObservableObject<String>, which is an observable wrapper of the string, not the string itself.
You need to get first the observable, and when the operation ends successfully you can retrieve the string:
if (textFile.exists()) {
FileClient fileClient = FileClient.create(textFile);
GluonObservableObject<String> retrieveObject = DataProvider
.retrieveObject(fileClient.createObjectDataReader(new StringInputConverter()));
retrieveObject.stateProperty().addListener((obs, ov, nv) -> {
if (ConnectState.SUCCEEDED.equals(nv)) {
loadedFile = retrieveObject.get();
}
});
}
This is a quick implementation of this functionality:
public class BasicView extends View {
private static final File ROOT_DIR;
static {
ROOT_DIR = Services.get(StorageService.class)
.flatMap(StorageService::getPrivateStorage)
.orElseThrow(() -> new RuntimeException("Error"));
}
private final File textFile;
private final TextField textField;
private String loadedFile = "none";
public BasicView(String name) {
super(name);
textFile = new File(ROOT_DIR, "text_file");
textField = new TextField();
VBox controls = new VBox(15.0, textField);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(30));
setCenter(controls);
}
#Override
protected void updateAppBar(AppBar appBar) {
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> System.out.println("Menu")));
appBar.setTitleText("Basic View");
appBar.getActionItems().add(MaterialDesignIcon.SAVE.button(e -> save()));
appBar.getActionItems().add(MaterialDesignIcon.RESTORE_PAGE.button(e -> restore()));
}
private void save() {
try (FileWriter writer = new FileWriter(textFile)) {
writer.write(textField.getText());
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void restore() {
if (textFile.exists()) {
FileClient fileClient = FileClient.create(textFile);
GluonObservableObject<String> retrieveObject = DataProvider
.retrieveObject(fileClient.createObjectDataReader(new StringInputConverter()));
retrieveObject.stateProperty().addListener((obs, ov, nv) -> {
if (ConnectState.SUCCEEDED.equals(nv)) {
loadedFile = retrieveObject.get();
textField.setText(loadedFile);
}
});
}
}
}
I'm using Xamarin Forms, Prism Unity, PCLStorage, & this.
So I'm having two problems; first, the View updates the property too late (i'm using OnNavigatedTo); and second, i'm lost on how to go about loading a local .pdf file using a custom webview.
LocalWebPage.xaml.cs
public partial class LocalWebPage : ContentPage
{
public LocalWebPage ()
{
InitializeComponent();
Redraw();
}
#region Methods
private void Redraw ()
{
StackLayout main = new StackLayout ()
{
Spacing = 0
};
LocalWebView webView = new LocalWebView ()
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand
};
webView.SetBinding(LocalWebView.UriProperty, "Source", BindingMode.TwoWay);
main.Children.Add(webView);
Content = main;
}
#endregion
}
LocalWebPageViewModel.cs
public class LocalWebPageViewModel : BindableBase, INavigationAware
{
#region Fields & Properties
private INavigationService _navigationService;
private string _source;
public string Source
{
get { return _source; }
set { SetProperty(ref _source, value); }
}
public DelegateCommand GoBackCommand { get; set; }
#endregion
public LocalWebPageViewModel (INavigationService navigationService)
{
_navigationService = navigationService;
GoBackCommand = new DelegateCommand(GoBack);
}
private void GoBack ()
{
_navigationService.GoBackAsync();
}
public void OnNavigatedFrom (NavigationParameters parameters)
{
}
public void OnNavigatedTo (NavigationParameters parameters)
{
if (parameters.ContainsKey("url"))
{
Source = parameters["url"] as string;
}
}
}
StorageService.cs
public static async Task SaveToCache (string fileName, byte[] buffer)
{
IFolder rootFolder = FileSystem.Current.LocalStorage;
IFolder folder = await rootFolder.CreateFolderAsync("Cache", CreationCollisionOption.OpenIfExists);
IFile file = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
System.Diagnostics.Debug.WriteLine("LocalStorage: (" + rootFolder.Path + ")");
System.Diagnostics.Debug.WriteLine("Saved to Cache: (" + file.Path + ")");
using (Stream stream = await file.OpenAsync(FileAccess.ReadAndWrite))
{
stream.Write(buffer, 0, buffer.Length);
}
}
LocalWebView.cs
public class LocalWebView : WebView
{
#region Static Fields & Properties
public static readonly BindableProperty UriProperty = BindableProperty.Create(nameof(Uri),
typeof(string),
typeof(LocalWebView),
default(string));
#endregion
#region Fields & Properties
public string Uri
{
get { return (string) GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
#endregion
}
LocalWebViewRenderer.cs
[assembly: ExportRenderer (typeof(LocalWebView), typeof(LocalWebViewRenderer))]
namespace MyProject.Droid.CustomRenderers
{
public class LocalWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
LocalWebView localWebView = Element as LocalWebView;
Control.Settings.AllowUniversalAccessFromFileURLs = true;
// Hardcoded for now because of problem 1.
string path = string.Format("file://{0}/{1}/{2}", PCLStorage.FileSystem.Current.LocalStorage.Path, "Cache", "Sample.pdf");
System.Diagnostics.Debug.WriteLine("LocalWebViewRenderer:Android: " + path);
Control.LoadUrl(string.Format("file:///android_asset/pdfjs/web/viewer.html?file={0}", path));
}
}
}
}
Output
[0:] LocalStorage: (/data/user/0/MyProject.Droid/files)
[0:] Saved to Cache: (/data/user/0/MyProject.Droid/files/Cache/Sample.pdf)
...
[0:] LocalWebViewRenderer:Android: file:///data/user/0/MyProject.Droid/files/Cache/Sample.pdf
08-30 17:49:04.104 W/BindingManager( 3709): Cannot call determinedVisibility() - never saw a connection for the pid: 3709
[0:] LocalWebPageViewModel.Source property changed: Sample.pdf
[INFO:CONSOLE(9665)] "Warning: Ignoring invalid character "60" in hex string", source: file:///android_asset/pdfjs/build/pdf.worker.js (9665)
08-30 17:49:04.921 I/chromium( 3709): [INFO:CONSOLE(9665)] "Warning: Ignoring invalid character "60" in hex string", source: file:///android_asset/pdfjs/build/pdf.worker.js (9665)
...
[INFO:CONSOLE(0)] "Unhandled promise rejection", source: file:///android_asset/pdfjs/web/viewer.html?file=file:///data/user/0/MyProject.Droid/files/Cache/Sample.pdf (0)
I had completed my App's home page in Xamarin.Forms Portable.
Now i want to add a Flotation Action Button In my Android Project !
Is there any way to add FAB for Android in my existing home page, which was coded in Xamarin.Forms Portable.
OR
I want to create a separate home page for Android and add call it as a MainPage for android ?
Thanks and Regards.
Before the official support library came out I ported the FAB over.
There is now a Xamarin.Forms sample in my GitHub repo that you can use: https://github.com/jamesmontemagno/FloatingActionButton-for-Xamarin.Android
Build a Custom Control
For the FAB's properties to be bindable in Xamarin.Forms, we need a custom control with bindable properties.
public class FloatingActionButtonView : View
{
public static readonly BindableProperty ImageNameProperty = BindableProperty.Create<FloatingActionButtonView,string>( p => p.ImageName, string.Empty);
public string ImageName
{
get { return (string)GetValue (ImageNameProperty); }
set { SetValue (ImageNameProperty, value); }
}
public static readonly BindableProperty ColorNormalProperty = BindableProperty.Create<FloatingActionButtonView,Color>( p => p.ColorNormal, Color.White);
public Color ColorNormal
{
get { return (Color)GetValue (ColorNormalProperty); }
set { SetValue (ColorNormalProperty, value); }
}
public static readonly BindableProperty ColorPressedProperty = BindableProperty.Create<FloatingActionButtonView,Color>( p => p.ColorPressed, Color.White);
public Color ColorPressed
{
get { return (Color)GetValue (ColorPressedProperty); }
set { SetValue (ColorPressedProperty, value); }
}
public static readonly BindableProperty ColorRippleProperty = BindableProperty.Create<FloatingActionButtonView,Color>( p => p.ColorRipple, Color.White);
public Color ColorRipple
{
get { return (Color)GetValue (ColorRippleProperty); }
set { SetValue (ColorRippleProperty, value); }
}
...
}
We will then map each property to a corresponding property on the native FAB control.
Attach a Renderer
If we want to use a native control in Xamarin.Forms, we need a renderer. For simplicity, lets use a ViewRenderer. This renderer will map our custom FloatingActionButtonView to an Android.Widget.FrameLayout.
public class FloatingActionButtonViewRenderer : ViewRenderer<FloatingActionButtonView, FrameLayout>
{
...
private readonly Android.Content.Context context;
private readonly FloatingActionButton fab;
public FloatingActionButtonViewRenderer()
{
context = Xamarin.Forms.Forms.Context;
fab = new FloatingActionButton(context);
...
}
protected override void OnElementChanged(ElementChangedEventArgs<FloatingActionButtonView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || this.Element == null)
return;
if (e.OldElement != null)
e.OldElement.PropertyChanged -= HandlePropertyChanged;
if (this.Element != null) {
//UpdateContent ();
this.Element.PropertyChanged += HandlePropertyChanged;
}
Element.Show = Show;
Element.Hide = Hide;
SetFabImage(Element.ImageName);
fab.ColorNormal = Element.ColorNormal.ToAndroid();
fab.ColorPressed = Element.ColorPressed.ToAndroid();
fab.ColorRipple = Element.ColorRipple.ToAndroid();
var frame = new FrameLayout(Forms.Context);
frame.RemoveAllViews();
frame.AddView(fab);
SetNativeControl (frame);
}
public void Show(bool animate = true)
{
fab.Show(animate);
}
public void Hide(bool animate = true)
{
fab.Hide(animate);
}
void HandlePropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Content") {
Tracker.UpdateLayout ();
}
else if (e.PropertyName == FloatingActionButtonView.ColorNormalProperty.PropertyName)
{
fab.ColorNormal = Element.ColorNormal.ToAndroid();
}
else if (e.PropertyName == FloatingActionButtonView.ColorPressedProperty.PropertyName)
{
fab.ColorPressed = Element.ColorPressed.ToAndroid();
}
else if (e.PropertyName == FloatingActionButtonView.ColorRippleProperty.PropertyName)
{
fab.ColorRipple = Element.ColorRipple.ToAndroid();
}
...
}
void SetFabImage(string imageName)
{
if(!string.IsNullOrWhiteSpace(imageName))
{
try
{
var drawableNameWithoutExtension = Path.GetFileNameWithoutExtension(imageName);
var resources = context.Resources;
var imageResourceName = resources.GetIdentifier(drawableNameWithoutExtension, "drawable", context.PackageName);
fab.SetImageBitmap(Android.Graphics.BitmapFactory.DecodeResource(context.Resources, imageResourceName));
}
catch(Exception ex)
{
throw new FileNotFoundException("There was no Android Drawable by that name.", ex);
}
}
}
}
Pull it all Together
OK! We've built the custom control, and mapped it to a renderer. The last step is laying out the control in our view.
public class MainPage : ContentPage
{
public MainPage()
{
var fab = new FloatingActionButtonView() {
ImageName = "ic_add.png",
ColorNormal = Color.FromHex("ff3498db"),
ColorPressed = Color.Black,
ColorRipple = Color.FromHex("ff3498db")
};
// Main page layout
var pageLayout = new StackLayout {
Children =
{
new Label {
VerticalOptions = LayoutOptions.CenterAndExpand,
XAlign = TextAlignment.Center,
Text = "Welcome to Xamarin Forms!"
}
}};
var absolute = new AbsoluteLayout() {
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand };
// Position the pageLayout to fill the entire screen.
// Manage positioning of child elements on the page by editing the pageLayout.
AbsoluteLayout.SetLayoutFlags(pageLayout, AbsoluteLayoutFlags.All);
AbsoluteLayout.SetLayoutBounds(pageLayout, new Rectangle(0f, 0f, 1f, 1f));
absolute.Children.Add(pageLayout);
// Overlay the FAB in the bottom-right corner
AbsoluteLayout.SetLayoutFlags(fab, AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutBounds(fab, new Rectangle(1f, 1f, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
absolute.Children.Add(fab);
Content = absolute;
}
}
Complete code on Github : Floating Action Button Xamarin.Forms
I am trying to bind the following class to a relaycommand.
public class UserAuth
{
public string UserName { get; set; }
public string Password { get; set; }
}
This is my MainActivity Class:
public partial class MainActivity : ActivityBaseEx
{
private Binding<string, UserAuth> _userInformation;
private Binding<string, UserAuth> _cool;
public LoginViewModel LoginViewModel
{
get
{
return App.Locator.Login;
}
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
_userInformation = this.SetBinding(()=> **....... WHAT GOES HERE!! I can do this for a simple string, but cannot figure it out for a class!**
// Get our button from the layout resource and attach an event to it
var signInButton = FindViewById<Button>(Resource.Id.btnSingIn);
signInButton.SetCommand("Click", LoginViewModel.LoginCommand, _userInformation);
}
}
This is my RelayCommand in my View Model
public RelayCommand<UserAuth> LoginCommand
{
get
{
return _loginCommand ?? (_loginCommand = new RelayCommand<UserAuth>(
async (userAuth) =>
{
_isLoading = true;
try
{
// var loggedIn = await _loginService.AuthenticateUser("emediaqa1", "p098765");
var loggedIn = await _loginService.AuthenticateUser(userAuth.UserName, userAuth.Password);
_isLoading = false;
}
catch (Exception ex)
{
var dialog = ServiceLocator.Current.GetInstance<IDialogService>();
dialog.ShowError(ex, "Error Authenticating", "OK", null);
}
_isLoading = false;
}));
}
}
My problem is with this line:
_userInformation = this.SetBinding(()=> // WHAT GOES HERE!! I can do this for a simple
//string, but cannot figure it out for a class!
Please help!
Thanks!!
I use something like this in the MainActivity.OnCreate:
_usernameBinding = this.SetBinding(() => Vm.userAuth.Username, () => Username.Text, BindingMode.TwoWay);
_passwordBinding = this.SetBinding(() => Vm.userAuth.Password, () => Password.Text, BindingMode.TwoWay);
So you need 2 bindings, one for userName and one for Password.