I'm deriving from UIApplication and overriding SendEvent to capture user taps.
Everything works fine, except when closing modals of type IMvxModalIosView.
Here's the method:
public override void SendEvent(UIEvent uievent)
{
NSSet touches = uievent.AllTouches;
if (touches != null)
{
UITouch touch = (UITouch)touches.AnyObject;
switch (touch.Phase)
{
case UITouchPhase.Ended:
StartDoingStuffAgain();
break;
default:
StopDoingStuff();
break;
}
}
base.SendEvent(uievent);
}
Debugging this while closing an IMvxModalIosView, if I set a breakpoint at the start of the method, as well as StartDoingStuffAgain(), the latter gets hit. If however I only set a breakpoint at StartDoingStuffAgain(), it never gets hit. StopDoingStuff() gets hit regardless.
Why is this?
Edit 1: As per nmilcoff's request for more code:
[Register("AppDelegate")]
public partial class AppDelegate : MvxApplicationDelegate
{
//...
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
//...
_app = application as MyUIApplication;
//...
}
//...
}
[Register("MyUIApplication")]
public class MyUIApplication : UIApplication
{
//...
// Bit of code in original question goes here.
//...
}
//The iPad view presenter (iPhone has MyIPhoneViewPresenter)
public class MyIPadViewPresenter : MvxBaseIosViewPresenter
{
//...
private UINavigationController _navController;
private UINavigationController _modalNavController;
private IMvxMessenger _messenger;
protected readonly SemaphoreSlim _autoLock = new SemaphoreSlim(1, 1);
//...
public override async void Show(MvxViewModelRequest request)
{
await _autoLock.WaitAsync();
//...
if(_messenger == null)
{
_messenger = Mvx.Resolve<IMvxMessenger>();
}
// Among other things, the following method checks if the page
// corresponding to the VM request is a Xamarin.Forms or a native view.
var presentation = MvxIOSPresentationHelpers.CreateView(request, TargetIdiom.Tablet);
switch(presentation.Type)
{
//...
case ViewType.ModalWithNav:
showNavModalView(presentation.ViewController);
break;
//...
}
messenger.Publish(new ViewModelShowMessage(request.ViewModelType, presentation.ViewController, this));
//..
_autoLock.Release();
}
//...
private void showNavModalView(UIViewController viewController)
{
if(_modalNavController == null)
{
_modalNavController = new ModalNavController(viewController);
_modalNavController.ModalPresentationStyle = viewController.ModalPresentationStyle;
_navController.PresentViewController(_modalNavController, false, delegate { });
}
else
{
_modalNavController.PushViewController(viewController, true);
}
//...
}
//...
public override async void ChangePresentation(MvxPresentationHint hint)
{
var close_hint = hint as MvxClosePresentationHint;
if (close_hint != null)
{
await _autoLock.WaitAsync();
close(close_hint.ViewModelToClose);
_autoLock.Release();
}
else
{
//...
}
}
//...
protected void close(IMvxViewModel toClose)
{
//...
_messenger.Publish(new MvxMessage(this));
//...
if (_modalNavController != null)
{
if(_modalNavController.ChildViewControllers.Length <= 1)
{
// This is where the close sequence comes down to.
_modalNavController.DismissViewController(true, delegate{ });
_modalNavController = null;
}
else
{
//...
}
return;
}
//..
}
//...
}
// The VM corresponding to the IMvxModalIosView
public class MyProblematicModalVM : MvxViewModel
{
//...
public ICommand CloseCommand
{
get { return new MvxCommand(() => Close(this)); }
}
//...
}
Related
I'm converting an app to use FreshMVVM from a non-MVVM format. When the app launches, there is a login page, then after login, a tabbed page with modal pages called from the buttons on each tabbed page.
Given the process I'm following, I figured that this would be a perfect option to use Michael Ridland's process to switch out NavigationStacks (https://github.com/rid00z/FreshMvvm#switching-out-navigationstacks-on-the-xamarinforms-mainpage), but the steps appear to be missing quite a few important instructions, and these steps are not in the sample app on the GitHub.
In my App.xaml.xs file, I have the following code:
public App()
{
InitializeComponent();
var loginPage = FreshMvvm.FreshPageModelResolver.ResolvePageModel<LoginPageModel>();
var loginContainer = new STNavigationContainer(loginPage, NavigationContainerNames.AuthenticationContainer);
var homePageViewContainer = new FreshTabbedNavigationContainer(NavigationContainerNames.MainContainer);
MainPage = loginContainer;
}
public FreshTabbedNavigationContainer(string navigationServiceName)
{
NavigationServiceName = navigationServiceName;
RegisterNavigation();
}
protected void RegisterNavigation()
{
FreshIOC.Container.Register<IFreshNavigationService>(this, NavigationServiceName)
}
public void SwitchOutRootNavigation(string navigationServiceName)
{
IFreshNavigationService rootNavigation =
FreshIOC.Container.Resolve<IFreshNavigationService>(navigationServiceName);
}
public void LoadTabbedNav()
{
var tabbedNavigation = new FreshTabbedNavigationContainer();
tabbedNavigation.AddTab<CharactersPageModel>("Characters", "characters.png");
tabbedNavigation.AddTab<AdventuresPageModel>("Adventures", "adventures.png");
tabbedNavigation.AddTab<AccountPageModel>("Account", "account.png");
MainPage = tabbedNavigation;
}
public class NavigationContainerNames
{
public const string AuthenticationContainer = "AuthenticationContainer";
public const string MainContainer = "MainContainer";
}
This seems to match with the steps provided in the ReadMe.md, but the calls to NavigationServiceName return an error, since there's no instruction on creating such a class or what it should contain, nor am I clear on where the FreshTabbedNavigationContainer or SwitchOutRootNavigation would be called.
Has anyone been able to get this to work? What steps am I missing on this?
EDIT: I forgot that I have extended the FreshNavigationContainter class and the FreshNavigationPage class. Here are my extended classes:
STNavigationContainer:
public class STNavigationContainer : FreshNavigationContainer
{
public STNavigationContainer(Page page) : base(page)
{
}
public STNavigationContainer(Page page, string navigationPageName) : base(page, navigationPageName)
{
}
protected override Page CreateContainerPage(Page page)
{
if (page is NavigationPage || page is MasterDetailPage || page is TabbedPage)
return page;
return new STNavigationPage(page);
}
}
STNavigationPage:
public class STNavigationPage : NavigationPage
{
public STNavigationPage()
{
}
public STNavigationPage(Page page) : base(page)
{
BarBackgroundColor = Color.FromHex("#2DAFEB");
BarTextColor = Color.FromHex("#C9371D");
}
}
Edit 2: Re-reading https://michaelridland.com/xamarin/implementing-freshmvvm-mvvm-xamarin-forms/ and the Github post, I was able to figure out that I needed to do this in a Custom Navigation Service, so here is my updated code.
App.xaml.cs:
public partial class App : Application
{
public static Account account = new Account();
public App()
{
InitializeComponent();
FreshIOC.Container.Register<IDatabaseService, DatabaseService>();
if (account.Equals(null))
{
LoadSingleNav();
}
else
{
LoadTabbedNav();
}
var navPage = new NavigationPage(new LoginPage())
{
BarBackgroundColor = Color.FromHex("#2DAFEB"),
BarTextColor = Color.FromHex("#C9371D")
};
NavigationPage.SetHasNavigationBar(navPage.CurrentPage, false);
MainPage = navPage;
MainPage = loginContainer;
}
public FreshTabbedNavigationContainer(string navigationServiceName)
{
NavigationContainerNames = navigationServiceName;
RegisterNavigation();
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
CustomNavService.cs:
public class CustomNavService : NavigationPage, IFreshNavigationService
{
FreshTabbedNavigationContainer _tabbedNavigationPage;
Page _charactersPage, _adventuresPage, _accountPage;
public CustomNavService(Page page) : base (page)
{
NavigationServiceName = "CustomNavService";
LoadTabbedNav();
CreateLoginPage();
RegisterNavigation();
}
public string NavigationServiceName { get; private set; }
public void NotifyChildrenPageWasPopped()
{
throw new NotImplementedException();
}
public async Task PopPage(bool modal = false, bool animate = true)
{
if (modal)
await Navigation.PopModalAsync (animate);
else
await Navigation.PopAsync (animate);
}
public async Task PopToRoot(bool animate = true)
{
await Navigation.PopToRootAsync(animate);
}
public async Task PushPage(Page page, FreshBasePageModel model, bool modal = false, bool animate = true)
{
if (modal)
await Navigation.PushModalAsync(page, animate);
else
await Navigation.PushAsync(page, animate);
}
public Task<FreshBasePageModel> SwitchSelectedRootPageModel<T>() where T : FreshBasePageModel
{
IFreshNavigationService rootNavigation =
FreshIOC.Container.Resolve<IFreshNavigationService>(NavigationServiceName);
}
public void LoadTabbedNav()
{
_tabbedNavigationPage = new FreshTabbedNavigationContainer();
_charactersPage = _tabbedNavigationPage.AddTab<CharactersPageModel>("Characters", "characters.png");
_adventuresPage = _tabbedNavigationPage.AddTab<AdventuresPageModel>("Adventures", "adventures.png");
_accountPage = _tabbedNavigationPage.AddTab<AccountPageModel>("Account", "account.png");
this = _tabbedNavigationPage;
}
private void CreateLoginPage()
{
var loginPage = FreshPageModelResolver.ResolvePageModel<LoginPageModel>(null);
var loginContainer = new STNavigationContainer(loginPage, CustomNavService.NavigationContainerNames.AuthenticationContainer);
var homePageViewContainer = new FreshTabbedNavigationContainer(CustomNavService.NavigationContainerNames.MainContainer);
}
protected void RegisterNavigation()
{
FreshIOC.Container.Register<IFreshNavigationService>(this, NavigationServiceName);
}
public class NavigationContainerNames
{
public const string AuthenticationContainer = "AuthenticationContainer";
public const string MainContainer = "MainContainer";
}
}
The SwitchSelectedRootPageModel<T> task in my Custom Navigation Service is showing that a return is needed, and I'm not quite clear on what the LoadTabbedNav is supposed to include with the this; the example shows this.Detail, but that's showing as an invalid reference.
Looking through our FreshMvvm implementation, our SwitchSelectedRootPageModel looks like this:
public Task<FreshBasePageModel> SwitchSelectedRootPageModel<T>() where T : FreshBasePageModel
{
return Task.FromResult<FreshBasePageModel>(null);
}
Our code correctly uses
this.Detail = _tabbedNavigationPage;
So if you are getting an invalid reference, there must be something else missing.
I'm developing an application for detecting motion within webcam frames.
For this, I'm using IBasicVideoEffect for extracting frames one by one from MediaCapture. I have created class CustomEffect which inherits IBasicVideoEffect. I have used OpenCV for motion detection, it is working fine. It is also giving me motion detection level. I want to raise event from CustomEffect if motion level is greater than threshold.
But for videoDefination code is:
var videoDefinition = new VideoEffectDefinition(typeof(CustomEffect).ToString());
Here for videoDefinition constructor it is asking for ClassID,
How can i get event from CustomEffect object.
I want to raise custom event from CustomEffect (eg.: MotionDetectedEvent )
Here is my CustomEffect class:
public sealed class CustomEffect : IBasicVideoEffect
{
private OpenCVHelper _helper;
private IPropertySet _configuration;
internal event EventHandler<EventArgs> MotionDetected;
public void SetProperties(IPropertySet configuration)
{
_configuration = configuration;
}
public void SetEncodingProperties(VideoEncodingProperties encodingProperties,
IDirect3DDevice device)
{
}
private bool IsToDetectMotion
{
get
{
object val;
if (_configuration != null &&
_configuration.TryGetValue("IsToDetectMotion", out val))
return (bool) val;
return false;
}
}
public void ProcessFrame(ProcessVideoFrameContext context)
{
var tempBitmap = context.OutputFrame.SoftwareBitmap;
context.InputFrame.SoftwareBitmap.CopyTo(tempBitmap);
var originalBitmap = SoftwareBitmap.Convert(tempBitmap, BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Straight);
var outputBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8,
originalBitmap.PixelWidth, originalBitmap.PixelHeight,
BitmapAlphaMode.Straight);
if (!IsToDetectMotion)
{
context.InputFrame.SoftwareBitmap.CopyTo(context.OutputFrame.SoftwareBitmap);
return;
}
if (_helper == null)
_helper = new OpenCVHelper();
var level = _helper.MotionDetector(tempBitmap, outputBitmap);
RaiseMotionDetectedEvent();
Debug.WriteLine(level.ToString());
outputBitmap.CopyTo(context.OutputFrame.SoftwareBitmap);
}
private void RaiseMotionDetectedEvent()
{
if (MotionDetected != null)
MotionDetected(this, new EventArgs());
}
public void Close(MediaEffectClosedReason reason)
{
}
public void DiscardQueuedFrames()
{
}
public bool IsReadOnly { get; }
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
var encodingProperties = new VideoEncodingProperties();
encodingProperties.Subtype = "ARGB32";
return new List<VideoEncodingProperties> {encodingProperties};
// If the list is empty, the encoding type will be ARGB32.
// return new List<VideoEncodingProperties>();
}
}
public MediaMemoryTypes SupportedMemoryTypes { get; }
public bool TimeIndependent { get; }
}
//in Windows Runtime Component
public sealed class FrameArgs
{
public FrameArgs(int frameCount)
{
FrameCount = frameCount;
}
public int FrameCount
{ get; }
}
public sealed partial class CustomEffect
{
#region ProcessFrameCompleted
public EventHandler<Object> ProcessFrameCompleted
{
get
{
object val;
if (configuration != null && configuration.TryGetValue(nameof(ProcessFrameCompleted), out val))
{
return (EventHandler<Object>)val;
}
return null;
}
}
public void RaiseProcessFrameCompleted(FrameArgs args)
{
ProcessFrameCompleted?.Invoke(null, (Object)args);
}
#endregion
//call as necessary
//RaiseProcessFrameCompleted(new FrameArgs(frameCount));
}
//in your app
public static async Task<IMediaExtension> AddCustomEffect(MediaCapture mediaCapture, EventHandler<FrameArgs> callBack)
{
if (mediaCapture == null)
{
throw new ArgumentException("Parameter cannot be null", nameof(mediaCapture));
}
var videoEffectDefinition =
// ReSharper disable once AssignNullToNotNullAttribute
new VideoEffectDefinition(typeof(CustomEffect).FullName);
var videoEffect =
await mediaCapture.AddVideoEffectAsync(videoEffectDefinition, MediaStreamType.VideoPreview);
videoEffect.SetProperties(
new PropertySet()
{
{
"ProcessFrameCompleted",
new EventHandler<object>((sender, e) =>
{
var args = (FrameArgs)e;
int frameCount = args.FrameCount;
callBack?.Invoke(sender, args);
})
}
});
return videoEffect;
}
Review the following code, where to take care of cases with both single and named binding for an interface, an abstract factory is used as suggested here
Parameterized Factories Using Ninject
Challenge here is, I need to introduce IEnumerable<T> bankingOperationList, instead of T bankingOperationList, since for named binding it will always use the abstract factory injection, Func<string,T> bankingOperationFunc, but if I don't use IEnumerable<T> suggested above it leads to exception, due to this for even non named single binding, I need to use something like:
bankingOperationList.FirstOrDefault().Withdraw(), even when I know there will only be one dependency.
Another challenge is, for some named bindings it has 30 - 40 bindings in few cases, which will be unnecessarily filled, when I can default T bankingOperationList to null, as it is not required. Please let me know, if the issue needs further clarification. Working Console project underneath.
public interface IBankingOperation
{
void Withdraw();
}
public class BankingOperationOne : IBankingOperation
{
public BankingOperationOne()
{
Console.WriteLine("Testing Constructor :: One :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation One");
}
}
public class BankingOperationTwo : IBankingOperation
{
public BankingOperationTwo()
{
Console.WriteLine("Testing Constructor :: Two :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation Two");
}
}
// Ninject Bindings
public class Bindings : NinjectModule
{
public override void Load()
{
Bind<IBankingOperation>().To<BankingOperationOne>()
.Named("A");
Bind<IBankingOperation>().To<BankingOperationTwo>()
.Named("B");
Bind<Func<string,IBankingOperation>>().ToMethod(ctx => name => ctx.Kernel.Get<IBankingOperation>(name));
}
}
public class BankTran<T> where T : IBankingOperation
{
private IEnumerable<T> bankingOperationList = null;
private Func<string,T> bankingOperationFunc;
public BankTran(IEnumerable<T> boList = null,
Func<string,T> boFunc = null)
{
bankingOperationList = boList;
bankingOperationFunc = boFunc;
}
public void DoOperation(string identifier = null)
{
if(bankingOperationFunc != null)
bankingOperationFunc(identifier).Withdraw();
else
bankingOperationList.FirstOrDefault().Withdraw();
Console.WriteLine("Transaction Successful ");
}
}
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel();
kernel.Load(Assembly.GetExecutingAssembly()); // Load from Bindings (derived from NinjectModule)
var transaction = kernel.Get<BankTran<IBankingOperation>>();
transaction.DoOperation("A");
}
}
Edit 1, based on response by jbl
public interface IBankingOperation<T>
{
void Withdraw();
}
public class BankingOperationOne : IBankingOperation<TestOne>
{
public BankingOperationOne()
{
Console.WriteLine("Testing Constructor :: One :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation One");
}
}
public class BankingOperationTwo : IBankingOperation<TestTwo>
{
public BankingOperationTwo()
{
Console.WriteLine("Testing Constructor :: Two :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation Two");
}
}
public class TestOne { }
public class TestTwo { }
// Ninject Bindings
public class Bindings : NinjectModule
{
public override void Load()
{
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("A");
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("B");
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().WhenInjectedInto(typeof(BankTran<TestOne>));
Bind<Func<string, IBankingOperation<TestOne>>>().ToMethod(ctx => name => ctx.Kernel.Get<IBankingOperation<TestOne>>(name));
Bind<IBankingOperation<TestTwo>>().To<BankingOperationTwo>();
}
}
public class BankTran<T> where T : class
{
private IBankingOperation<T> bankingOperation;
private Func<string, IBankingOperation<T>> bankingOperationFunc;
public BankTran(IBankingOperation<T> bo = null,
Func<string, IBankingOperation<T>> boFunc = null)
{
bankingOperation = bo;
bankingOperationFunc = boFunc;
}
public void DoOperation(string identifier = null)
{
if (bankingOperationFunc != null && identifier != null)
bankingOperationFunc(identifier).Withdraw();
else if (bankingOperation != null)
bankingOperation.Withdraw();
Console.WriteLine("Transaction Successful ");
}
}
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel(new NinjectSettings { AllowNullInjection = true});
kernel.Load(Assembly.GetExecutingAssembly()); // Load from Bindings (derived from NinjectModule)
var transaction = kernel.Get<BankTran<TestOne>>("A"); // Not Working
// var transaction = kernel.Get<BankTran<TestOne>>(); // Working
transaction.DoOperation();
}
}
Assuming BankingOperationOne is your default behaviour,
adding the following line in your Load method should allow to replace the IEnumerable<T> with T in your BankTran constructor :
Bind<IBankingOperation>().To<BankingOperationOne>().WhenInjectedInto(typeof(BankTran<>));
Another solution would be to just define a named binding for default behaviour
Bind<IBankingOperation>().To<BankingOperationOne>().Named("__DefaultBehaviour");
then
public void DoOperation(string identifier = "__DefaultBehaviour")
{
if (bankingOperationFunc != null)
bankingOperationFunc(identifier).Withdraw();
Console.WriteLine("Transaction Successful ");
}
Edit :
You should use the Ninject.Extenstions.Factory nuget package.
Using this package, the following code seems to fullfill you requirements.
public interface IBankingOperation<T>
{
void Withdraw();
}
public interface IBankingOperationFactory<T>
{
IBankingOperation<T> GetBankingOperation(string name);
}
public class BankingOperationOne : IBankingOperation<TestOne>
{
public BankingOperationOne()
{
Console.WriteLine("Testing Constructor :: One :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation One");
}
}
public class BankingOperationTwo : IBankingOperation<TestTwo>
{
public BankingOperationTwo()
{
Console.WriteLine("Testing Constructor :: Two :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation Two");
}
}
public class TestOne { }
public class TestTwo { }
// Ninject Bindings
public class Bindings : NinjectModule
{
public override void Load()
{
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("A");
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("B");
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().WhenInjectedInto(typeof(BankTran<TestOne>));
Bind<IBankingOperationFactory<IBankingOperation<TestOne>>>().ToFactory();
Bind<IBankingOperation<TestTwo>>().To<BankingOperationTwo>();
}
}
public class BankTran<T> where T : class
{
private IBankingOperation<T> bankingOperation;
private IBankingOperationFactory<T> _bankingOperationFactory;
public BankTran(IBankingOperation<T> bo = null,
IBankingOperationFactory<T> bankingOperationFactory = null)
{
bankingOperation = bo;
_bankingOperationFactory = bankingOperationFactory;
}
public void DoOperation(string identifier = null)
{
if (_bankingOperationFactory != null && identifier != null)
_bankingOperationFactory.GetBankingOperation(identifier).Withdraw();
else if (bankingOperation != null)
bankingOperation.Withdraw();
Console.WriteLine("Transaction Successful ");
}
}
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel(new NinjectSettings { AllowNullInjection = true });
kernel.Load(Assembly.GetExecutingAssembly()); // Load from Bindings (derived from NinjectModule)
var transaction = kernel.Get<BankTran<TestOne>>();
transaction.DoOperation();
transaction.DoOperation("A");
transaction.DoOperation("B");
}
}
I am trying to use a custom binding for UIActivityIndicatorView in mvvmcross but I am unable to get it working.
I followed the advice here. So I created the custom binder class:
public class ActivityIndicatorViewHiddenTargetBinding : MvxConvertingTargetBinding
{
public ActivityIndicatorViewHiddenTargetBinding(UIActivityIndicatorView target)
: base(target)
{
if (target == null)
{
MvxBindingTrace.Trace(
MvxTraceLevel.Error,
"Error - UIActivityIndicatorView is null in ActivityIndicatorViewHiddenTargetBinding");
}
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
public override System.Type TargetType
{
get { return typeof(bool); }
}
protected UIActivityIndicatorView View
{
get { return Target as UIActivityIndicatorView; }
}
protected override void SetValueImpl(object target, object value)
{
var view = (UIActivityIndicatorView)target;
if (view == null)
{
return;
}
view.Hidden = (bool)value;
if (view.Hidden)
{
view.StopAnimating();
}
else
{
view.StartAnimating();
}
}
}
And I tried to register.
public class Setup : MvxIosSetup
{
// ...
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
registry.RegisterPropertyInfoBindingFactory(
typeof(ActivityIndicatorViewHiddenTargetBinding),
typeof(UIActivityIndicatorView), "Hidden");
base.FillTargetFactories(registry);
}
// ...
}
The code that is setting up the binding:
public override void ViewDidLoad()
{
base.ViewDidLoad();
var bindingSet = this.CreateBindingSet<LoginView, LoginViewModel>();
bindingSet.Bind(Username).To(vm => vm.Username);
bindingSet.Bind(Password).To(vm => vm.Password);
bindingSet.Bind(LoginButton).To(vm => vm.LoginCommand);
//Progress is UIActivityIndicator
bindingSet.Bind(Progress)
.For(p => p.Hidden)
.To(vm => vm.IsLoading);
.WithConversion("Inverse");
bindingSet.Apply();
}
And the code in PCL library that is doing call to the server:
public class LoginViewModel : MvxViewModel
{
// ...
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set { SetProperty(ref _isLoading, value);}
}
private IMvxCommand _loginCommand;
public virtual IMvxCommand LoginCommand
{
get
{
_loginCommand = _loginCommand ?? new MvxAsyncCommand(AttemptLogin, CanExecuteLogin);
return _loginCommand;
}
}
private async Task AttemptLogin()
{
IsLoading = true;
try
{
await Task.Run(async () =>
{
await _loginService.LogIn(Username, Password);
});
ShowViewModel<MainViewModel>();
}
catch (SecurityClientException exc)
{
_dialogService.Alert(exc.Message, "Login Failed", "OK");
}
catch (Exception exc)
{
_dialogService.Alert($"Unknown error: {exc.Message}", "Login Failed", "OK");
}
finally
{
IsLoading = false;
}
}
// ...
}
However i have the following problems which I was unable to solve:
When the view loads, the spinner is visible and animating
When the command is executed, the spinner is not visible (I was trying version of the code with and without .WithConversion("Inverse");
When I put breakpoint into SetValueImpl method in ActivityIndicatorViewHiddenTargetBinding class, is not hit.
I am new to both mvvmcross and Xamarin. Does anyone have an idea what is wrong with the code?
I had a lot of problems when I tried to use FusedLocationApi from my Xamarin activity. The approach used by the code listed here Location Xamarin has been marked obsolete, so it didn't compile. My implementation is as follows. The question I have is, if this is the way to do it or if I am overlooking something much easier? The LocationHandler is used by my activity, e.g. OnCreate, OnResume, OnPause call the connect and disconnect methods. The OnChangedLocation method should of course do something more intelligent.
using System;
using Android.Gms.Common;
using Android.Gms.Common.Apis;
using Android.Gms.Location;
using Android.Locations;
using Android.Util;
using Android.OS;
using Android.Content;
namespace WithKidsAndroid
{
public class LocationHandler : Java.Lang.Object, IGoogleApiClientConnectionCallbacks, IGoogleApiClientOnConnectionFailedListener, Android.Gms.Location.ILocationListener
{
private IGoogleApiClient _googleAPI;
private Context _context;
public LocationHandler(Context context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
else
{
_context = context;
}
initializeGoogleAPI();
LocRequest = new LocationRequest();
}
public LocationHandler(Context context, LocationRequest request)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
else
{
_context = context;
}
initializeGoogleAPI();
LocRequest = request;
}
public LocationRequest LocRequest
{
get;
set;
}
public void connectGoogleAPI()
{
System.Diagnostics.Debug.Assert(_googleAPI != null);
if (!_googleAPI.IsConnectionCallbacksRegistered(this))
{
_googleAPI.RegisterConnectionCallbacks(this);
}
if (!_googleAPI.IsConnectionFailedListenerRegistered(this))
{
_googleAPI.RegisterConnectionFailedListener(this);
}
if (!_googleAPI.IsConnected || !_googleAPI.IsConnecting)
{
_googleAPI.Connect();
}
}
public void disconnectGoogleAPI()
{
if (_googleAPI != null && _googleAPI.IsConnected)
{
if (_googleAPI.IsConnectionCallbacksRegistered(this))
{
_googleAPI.UnregisterConnectionCallbacks(this);
}
if (_googleAPI.IsConnectionFailedListenerRegistered(this))
{
_googleAPI.UnregisterConnectionFailedListener(this);
}
_googleAPI.Disconnect();
}
}
public void OnConnected(Bundle connectionHint)
{
Log.Debug("LocationHandler", "logged connected", connectionHint);
if (LocRequest == null)
{
throw new Exception("Unknown location request. Set this first by using property LocRequest or constructor.");
}
LocationServices.FusedLocationApi.RequestLocationUpdates(_googleAPI, LocRequest, this);
}
public void OnConnectionSuspended(int cause)
{
Log.Debug("LocationHandler", "logged OnConnectionSuspended", cause);
}
public void OnConnectionFailed(ConnectionResult result)
{
Log.Debug("LocationHandler", "logged OnConnectionFailed", result);
}
public void OnLocationChanged(Location location)
{
Log.Debug("LocationHandler", "logged location changed: " + location.ToString());
}
private void initializeGoogleAPI()
{
int queryResult = GooglePlayServicesUtil.IsGooglePlayServicesAvailable(_context);
if (queryResult == ConnectionResult.Success)
{
_googleAPI = new GoogleApiClientBuilder(_context).AddApi(LocationServices.Api).AddConnectionCallbacks(this).AddOnConnectionFailedListener(this).Build();
}
else
{
var errorString = String.Format("There is a problem with Google Play Services on this device: {0} - {1}", queryResult, GooglePlayServicesUtil.GetErrorString(queryResult));
Log.Error("WithKidsAndroid.LocationHandler", errorString);
throw new Exception(errorString);
}
}
}
}
I guess not. I will close the question, but not remove the question as people can then find an example of LocationServices that work.