Notify Activity of changes in viewModel - xamarin.forms

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

Related

Android dev, What's best practice for timing update data in viewmodel

I want obey the MVVM struct in an android app. Now I have a situaion:
I want repeat get data from net and update the view.
So my code is like this:
public class FooVm() {
private MutalbeLiveData<Data> data = new MutableLiveData();
private Timer timer;
public LiveData<Data> getData() {
if (data == null) {
data = new MutableLivedata();
repeatRefresh()
}
}
private void repeatRefersh() {
timer = new Timer();
// schedule at fixed rate get data from net.
}
#override
public void onClear() {
if (timer == null) {
timer.cancel();
timer = null;
}
}
}
Now question is: if I want pause refresh when then view(for example, an Activity) onStop and resume refresh when view onStart. What's the right way to do thi?
Create another methods in viewModel , stopTimer() and resumeTimer()
override onStop() and onResume() in the activity
call viewModel.stopTimer() and viewModel.resumeTimer() before super method.
override fun onStop() {
viewModel.stopTimer()
super.onStop()
}
override fun onResume(){
viewModel.resumeTimer()
super.onResume()
}
I write it in kotlin . but you get the idea.

System.NotSupportedException: Unable to activate instance of type MyProject.CancelListener from native handle

In my Xamarin Forms app, I am using native Android code for the platform. I want to show a AlertDialog.Builder and catch the event when the user taps outside of the dialog box with SetOnCancelListener. This is my code:
AlertDialog.Builder adb = new AlertDialog.Builder(this);
adb.SetTitle("title");
adb.SetItems(myItems.Select(x => x.Name).ToArray(), (s, e) =>
{
// not important code
});
var cancelled = new CancelListener();
cancelled.Cancelled += (s, e) =>
{
// not important code
};
adb.SetCancelable(true);
adb.SetOnCancelListener(cancelled);
Dialog d = adb.Create();
d.Show();
My CancelListener class:
public class CancelListener : Java.Lang.Object, IDialogInterfaceOnCancelListener
{
public event EventHandler Cancelled;
public IntPtr Handle => IntPtr.Zero;
public CancelListener() : base()
{
}
public void Dispose()
{
Cancelled = null;
}
public void OnCancel(IDialogInterface dialog)
{
Cancelled?.Invoke(null, EventArgs.Empty);
}
}
When I tap outside of the dialog box I get the message:
System.NotSupportedException: Unable to activate instance of type
MyProject.CancelListener from native handle

Xamarin forms check if keyboard is open or not

Is there any way to check if keyboard is open or not in Xamarin Forms? Are there any events getting fired when the keyboard opens or closes? If so, where can I find an example of it?
I don't believe that there's a Xamarin.Forms way of doing it. Anyway, for the different platforms (at least Android and iOS) there is a way to achieve what you want.
Android
Under android there is InputMethodManager class. You can obtain it from your activity
var inputMethodManager = (InputMethodManager)this.GetSystemService(Context.InputMethodService);
Now you can check if the keyboard is shown with
var keyboardIsShown = inputMethodManager.IsAcceptingText;
According to this article on CodeProject you can use a class derived from IOnGlobalLayoutListener to listen to global layout events. When this event has fired, you can use the code above to check, if the layout has been changed due to the keyboard popping up.
iOS
Under iOS you may use UIKeyboard class which allows you to observe the DidShowNotification (see here).
notification = UIKeyboard.Notifications.ObserveDidShow ((sender, args) => {
Debug.WriteLine("Keyboard is shown.");
// whatever
});
similarly you can observe DidHideNotification (and some others - see here).
Xamarin.Forms
To implement the keyboard-notification in your Xamarin.Forms the easiest way will be to implement platform dependencies which are resolved with the DependencyService. To do this, you'll first have to introduce an interface for the platform service.
public interface IKeyboardService
{
event EventHandler KeyboardIsShown;
event EventHandler KeyboardIsHidden;
}
In your platform specific projects you'll have to implement the functionality in a platform specific way. See the following code section for iOS implementation
[assembly: Xamarin.Forms.Dependency(typeof(Your.iOS.Namespace.KeyboardService))]
namespace Your.iOS.Namespace
{
public class KeyboardService : IKeyboardService
{
public event EventHandler KeyboardIsShown;
public event EventHandler KeyboardIsHidden;
public KeyboardService()
{
SubscribeEvents();
}
private void SubscribeEvents()
{
UIKeyboard.Notifications.ObserveDidShow(OnKeyboardDidShow);
UIKeyboard.Notifications.ObserveDidHode(OnKeyboardDidHide);
}
private void OnKeyboardDidShow(object sender, EventArgs e)
{
KeyboardIsShown?.Invoke(this, EventArgs.Empty);
}
private void OnKeyboardDidHide(object sender, EventArgs e)
{
KeyboardIsHidden?.Invoke(this, EventArgs.Empty);
}
}
}
The Xamarin.Forms.Dependency makes the class visible to the DependencyService. See the following code for Android implementation
[assembly: Xamarin.Forms.Dependency(typeof(Your.Android.Namespace.KeyboardService))]
namespace Your.Android.Namespace
{
public class KeyboardService : IKeyboardService
{
public event EventHandler KeyboardIsShown;
public event EventHandler KeyboardIsHidden;
private InputMethodManager inputMethodManager;
private bool wasShown = false;
public KeyboardService()
{
GetInputMethodManager();
SubscribeEvents();
}
public void OnGlobalLayout(object sender, EventArgs args)
{
GetInputMethodManager();
if(!wasShown && IsCurrentlyShown())
{
KeyboardIsShown?.Invoke(this, EventArgs.Empty);
wasShown = true;
}
else if(wasShown && !IsCurrentlyShown())
{
KeyboardIsHidden?.Invoke(this, EventArgs.Empty);
wasShown = false;
}
}
private bool IsCurrentlyShown()
{
return inputMethodManager.IsAcceptingText;
}
private void GetInputMethodManager()
{
if (inputMethodManager == null || inputMethodManager.Handle == IntPtr.Zero)
{
inputMethodManager = (InputMethodManager)this.GetSystemService(Context.InputMethodService);
}
}
private void SubscribeEvents()
{
((Activity)Xamarin.Forms.Forms.Context).Window.DecorView.ViewTreeObserver.GlobalLayout += this.OnGlobalLayout;
}
}
}
In your Xamarin.Forms app you can now obtain an instance of the correct implementation of IKeyboardService with
var keyboardService = Xamarin.Forms.DependencyService.Get<IKeyboardService>();
In Xamarin Forms in ANDROID CODE change
(InputMethodManager)this.GetSystemService(Context.InputMethodService);
with
(InputMethodManager)Xamarin.Forms.Forms.Context.GetSystemService(Context.InputMethodService);
You need to change:
var inputMethodManager = (InputMethodManager)this.GetSystemService(Context.InputMethodService);
To:
InputMethodManager inputMethodManager = (InputMethodManager)((Activity)Android.App.Application.Context).GetSystemService(Context.InputMethodService);

Test case for fragment in android

In my application, I have multiple fragments on a single activity. Now I want to write a test case to check if these fragments are loading properly. To begin with, I passed some touch event to scroll to a particular fragment and then I am trying to fetch the name of this fragment. Below is my code for the test case:-
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity>
{
MainActivity mMainActivity;
ActionBar tactionbar;
Fragment tFragment;
public static final int TEST_POSITION = 2;
private static String mSelection ;
private int mPos = 0;
public MainActivityTest()
{
super(MainActivity.class);
}
protected void setUp() throws Exception
{
super.setUp();
mMainActivity = (MainActivity) getActivity();
tactionbar = mfoneclay.getActionBar();
}
public void testPreConditions()
{
assertNotNull(mMainActivity);
assertNotNull(tactionbar);
}
public void testFragmentUI()
{
mMainActivity.runOnUiThread(
new Runnable(){
public void run()
{
mMainActivity.getCurrentFocus();
}
});
for (int i = 1; i <= TEST_POSITION; i++)
{
this.sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
mPos = tactionbar.getSelectedNavigationIndex();
}
this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
mSelection = (String)tactionbar.getTabAt(mPos).getText();
String resultText = "Exclusive";
assertEquals(resultText,mSelection);
}
}
Here, "Exclusive" is the name of one of my tab to which I am navigating to via the touch event. Now, while running the test case, I can see that it is properly navigating to the "Exclusive" fragment, but the result shows the value of the msection variable as the name of the activity and not the fragments name. What am I doing wrong?
Got the solution. It was so stupid of me to use the wrong components to fetch the fragment. It turns out that I have to use "ViewPager" to fetch the fragments.

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