Xamarin.Forms Update Label Text From a Service - xamarin.forms

I have a Label on MainPage.xaml. I can edit Label Text from MainPage.xaml.cs.
There is a foregroung service running as well. There is function in this service to check a value from SQLite DB for every 10 secs. When value changes, Label text should be updated. I tried binding but it is a bit confusing. I manage updating by using like this: (foreground service timer changes App.SomeValue)
protected override void OnAppearing()
{
lblSyncID.Text = App.SomeValue;
}
But I need to see changes without OnAppearing or any other navigation change.
EDIT:
With #Jason's suggestion I used Messaging Center (and also binding) and it works now:
MainPage.xaml:
<Label Text="{Binding AppWaitingRecordValue}" ...
MainPage.xaml.cs:
public partial class MainPage : ContentPage
{
private string appWaitingRecordValue;
public string AppWaitingRecordValue
{
get { return appWaitingRecordValue; }
set
{
appWaitingRecordValue = value;
OnPropertyChanged(nameof(AppWaitingRecordValue));
}
}
public MainPage()
{
InitializeComponent();
BindingContext = this;
AppWaitingRecordValue = "0";
MessagingCenter.Subscribe<App>((App)Application.Current, "AppRecord", (sender) =>
{
AppWaitingRecordValue = App.recordWaiting.ToString();
});
}
.
.
TimestampService.cs (from Project.Android):
// get i from DB
App.recordWaiting = i;
Xamarin.Forms.MessagingCenter.Send<App>((App)Xamarin.Forms.Application.Current, "AppRecord");

Related

Notify Activity of changes in viewModel

I try to exit 'lock task mode' in Xamarin Android app. Here is what I am trying to achieve:
User taps on label (view in Xamarin.Forms) -> it cause change in ViewModel's boolean property to true
MainActivity (Xamarin.Android) observe that property has changed to true -> it makes application exit 'lock task mode'
My viewModel is placed in Xamarin.Forms 'App.xaml' class so it is accessible in Forms and Android part.
How Can I notify my Activity that property has changed so it can exit locked mode? I know this is propably very poor workaround, I would love to hear any advices and tips to make it more professional.
Thank you in advance!
EDIT
So the point is that I have got ViewModel with boolean property exitLockMode which indicates if app should be in lock mode or not:
public class AdminViewModel : BaseViewModel
{
//Number of taps to touch at main banner in 'MainPage' to open Admin Window
private int _tapsRequiredToAdmin = 5;
//Number of tolerance in miliseconds between next taps
private int _toleranceInMs = 1000;
private bool _exitLockMode = false;
public int ToleranceInMs { get => _toleranceInMs; }
public int TapsRequiredToAdmin { get => _tapsRequiredToAdmin; }
public bool ExitLockMode
{
get => _exitLockMode;
set => _exitLockMode=value;
}
}
AdminViewModel is created in 'App.xaml' class:
public partial class App : Application
{
private static AdminViewModel _adminViewModel;
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
public static AdminViewModel AdminViewModel
{
get
{
if(_adminViewModel == null )
_adminViewModel = new AdminViewModel();
return _adminViewModel;
}
}
protected override void OnStart() { }
protected override void OnSleep() { }
protected override void OnResume() { }
}
In my main view (Xamarin.Forms) I have got label where admin want to tap few times in order to exit lock mode:
private DateTime? LastTap = null;
private byte NumberOfTaps = 0;
AdminViewModel adminViewModel = App.AdminViewModel;
**********************************************
//This is method binded to Label in <TapGestureRecognizer Tapped="OnLabelTapped">
private async void OnLabelTapped(object sender, EventArgs e)
{
if (LastTap == null || (DateTime.Now - LastTap.Value).TotalMilliseconds < adminViewModel.ToleranceInMs)
{
if (NumberOfTaps == (adminViewModel.TapsRequiredToAdmin - 1))
{
NumberOfTaps = 0;
LastTap = null;
adminViewModel.ExitLockMode = true;
return;
}
else
{
NumberOfTaps++;
LastTap = DateTime.Now;
}
}
else
{
NumberOfTaps = 1;
LastTap = DateTime.Now;
}
}
Now I want to achieve that when I turn 'ExitLockMode' bool to true, it notify my 'MainActivity' (Xamarin.Android) to fire 'StopLockTask()' method. I know that in native Android it could be handled by observing bool property, but I don't know how to do it here.
I am newbie so it could be very messy, every help appreciated.
As Jason said, you can use messagecenter.The Xamarin.Forms MessagingCenter class implements the publish-subscribe pattern, allowing message-based communication between components that are inconvenient to link by object and type references.
This mechanism allows publishers and subscribers to communicate without having a reference to each other, helping to reduce dependencies between them.
You can follow this document and the sample in it https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/messaging-center

C#, Xamarin Forms: No Custom TextChangedEvent Raised on initialization

I'm creating an Xamarin.Forms MVVM App (only using Android) which needs certain buttons to be outlined red, whenever their text property holds a specific value. (Purpose: alert the user to press the button and select a value, which will change the Button Text Property and therefore remove the red outline)
To achieve this I've create the following documents:
A custom button CButton that extents the default Button:
public class CButton : Button
{
// this Hides the Default .Text-Property
public string Text
{
get => base.Text;
set
{
base.Text = value;
TextChangedEvent(this, new EventArgs());
}
}
// The Raised Event
protected virtual void TextChangedEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handler = TextChanged;
handler(sender, e);
}
public event EventHandler<EventArgs> TextChanged;
}
A custom behavior makes use of the raised TextChangedEvent
public class ButtonValBehavior : Behavior<CButton>
{
protected override void OnAttachedTo(CButton bindable)
{
bindable.TextChanged += HandleTextChanged;
base.OnAttachedTo(bindable);
}
void HandleTextChanged(object sender, EventArgs e)
{
string forbidden = "hh:mm|dd.mm.yyyy";
if (forbidden.Contains((sender as CButton).Text.ToLower()))
{
//Do when Button Text = "hh:mm" || "dd.mm.yyyy"
(sender as CButton).BorderColor = Color.Gray;
}
else
{
//Do whenever Button.Text is any other value
(sender as CButton).BorderColor = Color.FromHex("#d10f32");
}
}
protected override void OnDetachingFrom(CButton bindable)
{
bindable.TextChanged -= HandleTextChanged;
base.OnDetachingFrom(bindable);
}
}
The relevant parts of the ViewModel look the following:
public class VM_DIVI : VM_Base
{
public VM_DIVI(O_BasisProtokoll base)
{
Base = base;
}
private O_BasisProtokoll _base = null;
public O_BasisProtokoll Base
{
get => _base;
set
{
_base = value;
OnPropertyChanged();
}
}
Command _datePopCommand;
public Command DatePopCommand
{
get
{
return _datePopCommand ?? (_datePopCommand = new Command(param => ExecuteDatePopCommand(param)));
}
}
void ExecuteDatePopCommand(object param)
{
//launch popup
var p = new PP_DatePicker(param);
PopupNavigation.Instance.PushAsync(p);
}
}
The .xmal looks the following (b is the xmlns of the Namespace):
<b:CButton x:Name="BTN_ED_Datum"
Text="{Binding Base.ED_datum, Mode=TwoWay}"
Grid.Column="1"
Command="{Binding DatePopCommand}"
CommandParameter="{x:Reference BTN_ED_Datum}">
<b:CButton.Behaviors>
<b:ButtonValBehavior/>
</b:CButton.Behaviors>
</b:CButton>
This solution works fine whenever the input is caused by user interaction. However, when a Value is assigned during the initialization of the Page no red outline is created, in fact the TextChangedEvent isn't raised. By using breakpoints I noticed that during initialization the Text Property of CButton is never set, eventhough it actually will be in the view.
Despite fiddling around with my solution I cannot make this work on initialization. I tried to work around this issue by outlining every button by default in their constructor, however this will outline every button red, even when their text value doesn't require them to be.
How can I achieve my initial goal?
Many thanks in advance!
It's been a while but if I recall correctly what I ended up doing was:
Changing the new Text-Property of my custom Button to CText and
Making sure that I have Mode=TwoWay activated for any Element, that doesn't have it enabled by default. (Look up Binding modes on msdn for more)
making CText a bindable property of CButton
My custom button now looks the following:
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace EORG_Anton.Model
{
public class CButton : Button
{
public static readonly BindableProperty CTextProperty =
BindableProperty.Create(nameof(CText),
typeof(string),
typeof(CButton),
default(string),
BindingMode.TwoWay,
propertyChanged: OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (CButton)bindable;
var value = (string)newValue;
control.CText = value;
}
public string CText
{
get => base.Text;
set
{
base.Text = value;
TextChangedEvent(this, new EventArgs());
}
}
protected virtual void TextChangedEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handler = TextChanged;
handler(sender, e);
}
public event EventHandler<EventArgs> TextChanged;
}
}

DisplayAlert from ViewModel not displaying

I need to display DisplayAlert from the View Model, however its simply doesn't display. Is there some other way how to display alert from the VM? The permission is true so that works.
private async Task TakePicture()
{
await Permission();
var imageSource = Application.Current.MainPage.DisplayActionSheet(AppResources.AlertNewPhoto, AppResources.AlertNewPhoto, AppResources.AlertGallery);
if (imageSource.Result == AppResources.AlertNewPhoto)
}
You can change your constructor of ViewModel like following code.
public PersonsViewModel(ContentPage page){
page.DisplayAlert("info","test","Ok");
}
In your Layout background code, you can use it following code.
public partial class MainPage : ContentPage
{
PersonsViewModel personsViewModel;
public MainPage()
{
InitializeComponent();
personsViewModel = new PersonsViewModel(this);
this.BindingContext = personsViewModel;
}
If you can use plugin, you can use ACR.UserDialogs. https://github.com/aritchie/userdialogs
I solved this problem using events
public MainPageVewModel()
{
Application.Current.MainPage.Loaded += LoadCards;
}
private async void LoadCards(object sender, EventArgs e)
{
// your code on View Loaded
await Application.Current.MainPage.DisplayAlert("working alert", "alert", "ok");
}

UWP Change loading state using binding and async functions

I am coming from an Angular 2 and a C# back end background, so for the Angular side of things I am used to working with async functions and code, as well the C# background I understand the base libraries.
I am trying to create a simple page that has a a button, and a loading gif. You click the button the loading gif appears, 10 seconds later it disappears.
I can make the loading start no problem, but the nature of the async code jumps the execution and instantly makes the gif disappear.
How do I go about starting the spinner / making a gif visible, waiting 10 seconds in a non ui-blocking manner, and then finish with a thread-safe way of ending the animation / gif visibility?
View-Model code:
public class LoadingViewModel: INotifyPropertyChanged
{
private Visibility _loadingState;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public LoadingViewModel()
{
this._loadingState = Visibility.Collapsed;
}
public Visibility LoadingState
{
get {
return this._loadingState;
}
set {
this._loadingState = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
MainView.xaml.cs:
public LoadingViewModel LoadingViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.LoadingViewModel = new LoadingViewModel();
}
private async Task BeginLoading()
{
LoadingViewModel.LoadingState = Visibility.Visible;
await Task.Factory.StartNew(() =>
{
Task.Delay(TimeSpan.FromSeconds(10));
}).ContinueWith(EndLoadingState);
}
//Updated and works but is there a better way?
private async Task BeginLoading()
{
LoadingViewModel.LoadingState = Visibility.Visible;
await Task.Factory.StartNew(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(10));
await EndLoadingState(); //<-- New EndLoadingState doesn't accept parms
});
}
private async void EndLoadingState(object state)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
LoadingViewModel.LoadingState = Visibility.Collapsed;
});
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
await BeginLoading();
}
And lastly a basic stack panel with my button and image:
<StackPanel Margin="10,144,0,144">
<Button Content="Begin Loading for 10 seconds" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0" Height="157" Width="366" FontSize="22" Background="{x:Null}" BorderThickness="5" BorderBrush="#FF58FF00" Click="Button_Click"/>
<Image HorizontalAlignment="Center" Height="250" VerticalAlignment="Center" Width="250" Margin="0,25,0,0" Stretch="UniformToFill" Source="Assets/LoadingBubbles.gif" Visibility="{x:Bind Path=LoadingViewModel.LoadingState, Mode=TwoWay}"/>
</StackPanel>
First, try using a bool property in your LoadingViewModel instead of Visibility as the latter is a UI attribute. You generally don't want that in your ViewModel. If your target version of Windows 10 is 14393 or higher, you can bind it directly without a BoolToVisibilityConverter. And the binding doesn't need to be TwoWay also.
Visibility="{x:Bind Path=LoadingViewModel.IsLoading, Mode=OneWay}"
Second, XAML binding will actually take care of dispatching the updated value onto the UI thread. So you can also get rid of Dispatcher.RunAsync and have a normal void method
private void EndLoadingState(object state)
{
LoadingViewModel.IsLoading = false;
}
Finally, your BeginLoading method(best to rename it to BeginLoadingAsync) can be simplified to
private async Task BeginLoadingAsync()
{
LoadingViewModel.IsLoading = true;
await Task.Delay(TimeSpan.FromSeconds(10));
EndLoadingState();
}
Hope this helps!

Structuring a MonoTouch.Dialog application

From the examples at Xamarin.com you can build basic M.T. Dialog apps, but how do you build a real life application?
Do you:
1) Create a single DialogViewController and tree every view/RootElement from there or,
2) Create a DialogViewController for every view and use the UINavigationController and push it on as needed?
Depending on your answer, the better response is how? I've built the example task app, so I understand adding elements to a table, click it to go to the 'next' view for editing, but how to click for non-editing? How to click a button, go next view if answer is number 1?
Revised:
There is probably no one right answer, but what I've come up with seems to work for us. Number 2 from above is what was chosen, below is an example of the code as it currently exists. What we did was create a navigation controller in AppDelegate and give access to it throughout the whole application like this:
public partial class AppDelegate : UIApplicationDelegate
{
public UIWindow window { get; private set; }
//< There's a Window property/field which we chose not to bother with
public static AppDelegate Current { get; private set; }
public UINavigationController NavController { get; private set; }
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
Current = this;
window = new UIWindow (UIScreen.MainScreen.Bounds);
NavController = new UINavigationController();
// See About Controller below
DialogViewController about = new AboutController();
NavController.PushViewController(about, true);
window.RootViewController = NavController;
window.MakeKeyAndVisible ();
return true;
}
}
Then every Dialog has a structure like this:
public class AboutController : DialogViewController
{
public delegate void D(AboutController dvc);
public event D ViewLoaded = delegate { };
static About about;
public AboutController()
: base(about = new About())
{
Autorotate = true;
about.SetDialogViewController(this);
}
public override void LoadView()
{
base.LoadView();
ViewLoaded(this);
}
}
public class About : RootElement
{
static AboutModel about = AboutVM.About;
public About()
: base(about.Title)
{
string[] message = about.Text.Split(...);
Add(new Section(){
new AboutMessage(message[0]),
new About_Image(about),
new AboutMessage(message[1]),
});
}
internal void SetDialogViewController(AboutController dvc)
{
var next = new UIBarButtonItem(UIBarButtonSystemItem.Play);
dvc.NavigationItem.RightBarButtonItem = next;
dvc.ViewLoaded += new AboutController.D(dvc_ViewLoaded);
next.Clicked += new System.EventHandler(next_Clicked);
}
void next_Clicked(object sender, System.EventArgs e)
{
// Load next controller
AppDelegate.Current.NavController.PushViewController(new IssuesController(), true);
}
void dvc_ViewLoaded(AboutController dvc)
{
// Swipe location: https://gist.github.com/2884348
dvc.View.Swipe(UISwipeGestureRecognizerDirection.Left).Event +=
delegate { next_Clicked(null, null); };
}
}
Create a sub-class of elements as needed:
public class About_Image : Element, IElementSizing
{
static NSString skey = new NSString("About_Image");
AboutModel about;
UIImage image;
public About_Image(AboutModel about)
: base(string.Empty)
{
this.about = about;
FileInfo imageFile = App.LibraryFile(about.Image ?? "filler.png");
if (imageFile.Exists)
{
float size = 240;
image = UIImage.FromFile(imageFile.FullName);
var resizer = new ImageResizer(image);
resizer.Resize(size, size);
image = resizer.ModifiedImage;
}
}
public override UITableViewCell GetCell(UITableView tv)
{
var cell = tv.DequeueReusableCell(skey);
if (cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Default, skey)
{
SelectionStyle = UITableViewCellSelectionStyle.None,
Accessory = UITableViewCellAccessory.None,
};
}
if (null != image)
{
cell.ImageView.ContentMode = UIViewContentMode.Center;
cell.ImageView.Image = image;
}
return cell;
}
public float GetHeight(UITableView tableView, NSIndexPath indexPath)
{
float height = 100;
if (null != image)
height = image.Size.Height;
return height;
}
public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath indexPath)
{
//base.Selected(dvc, tableView, path);
tableView.DeselectRow(indexPath, true);
}
}
#miquel
The current idea of a workflow is an app that starts with a jpg of the Default.png that fades into the first view, with a flow control button(s) that would move to the main app. This view, which I had working previous to M.T.D. (MonoTouch.Dialog), which is a table of text rows with an image. When each row is clicked, it moves to another view that has the row/text in more detail.
The app also supports in-app-purchasing, so if the client wishes to purchase more of the product, then switch to another view to transact the purchase(s). This part was the main reason for switching to M.T.D., as I thought M.T.D. would be perfect for it.
Lastly there would be a settings view to re-enable purchases, etc.
PS How does one know when the app is un-minimized? We would like to show the fade in image again.
I have been asking myself the same questions. I've used the Funq Dependency Injection framework and I create a new DialogViewController for each view. It's effectively the same approach I've used previously developing ASP.NET MVC applications and means I can keep the controller logic nicely separated. I subclass DialogViewController for each view which allows me to pass in to the controller any application data required for that particular controller. I'm not sure if this is the recommended approach but so far it's working for me.
I too have looked at the TweetStation application and I find it a useful reference but the associated documentation specifically says that it isn't trying to be an example of how to structure a MonoTouch application.
I use option 2 that you stated as well, it works pretty nicely as you're able to edit the toolbar options on a per-root-view basis and such.
Option 2 is more feasible, as it also gives you more control on each DialogViewController. It can also helps if you want to conditionally load the view.

Resources