I have two Activities like
[Activity(Label = "View for FirstViewModel", MainLauncher = true)]
public class FirstView : MvxActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.FirstView);
}
}
[Activity(Label = "View for SecondViewModel", MainLauncher = false)]
public class SecondView : MvxActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.SecondView);
}
}
and two ViewModels
public class FirstViewModel
: MvxViewModel
{
private string _hello = "Hello MvvmCross 1";
public FirstViewModel()
{
}
public string Hello
{
get { return _hello; }
set { _hello = value; RaisePropertyChanged(() => Hello); }
}
public ICommand Next
{
get
{
return new MvxCommand(() => ShowViewModel<SecondViewModel>());
}
}
}
public class SecondViewModel : MvxViewModel
{
public SecondViewModel()
{
}
private string _hello = "Hello MvvmCross 2";
public string Hello
{
get { return _hello; }
set { _hello = value; RaisePropertyChanged(() => Hello); }
}
public ICommand Back
{
get
{
return new MvxCommand(() => ShowViewModel<FirstViewModel>());
}
}
}
I'm trying just two switch back and forth between them. the back step just does not work. Logging looks okay to me. Using the same VMs with an WPF project works just fine. So what the problem with androids activities?
Thanks in advance!
MH
Related
I am currently migrating my Xamarin.Forms app to .NET MAUI, and having a difficulty in migrating view renderer. In .NET MAUI I am using camera2 in my app, and using the renderer for same.
My Xamarin forms code is
public class CameraRecordV3 : View
{
public static readonly BindableProperty StartProperty = BindableProperty.Create(
"Start", typeof(int), typeof(int), 6000);
public int Start
{
set { SetValue(StartProperty, value); }
get { return (int)GetValue(StartProperty); }
}
}
using iVue.Views;
using System.ComponentModel;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.Handlers.Compatibility;
namespace iVue.Platforms.Android.Renderers;
public class CameraRecordRenderer_V3 : ViewRenderer<CameraRecordV3, CameraRecordControl_V3>
{
private CameraRecordControl_V3 _cameraControl;
private DisplayTimeHelper _displayTimeHelper = new DisplayTimeHelper();
public CameraRecordRenderer_V3(Context context)
: base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<CameraRecordV3> e)
{
base.OnElementChanged(e);
if (Control == null)
{
_cameraControl = new CameraRecordControl_V3(Context, e.NewElement);
SetNativeControl(_cameraControl);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var model = (CameraRecordV3)sender;
base.OnElementPropertyChanged(sender, e);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_cameraControl.Dispose();
if(Control != null)
Control.Dispose();
}
}
}
CameraRecordControl_V3 is a viewgroup which contains a native view for android, which contains buttons and camera
public class CameraRecordControl_V3 : ViewGroup
{
public CameraRecordControl_V3(Context context, CameraRecordV3 vm) : base(context)
{
_activity = this.Context as Activity;
_view = _activity.LayoutInflater.Inflate(Resource.Layout.CameraRecordLayoutV2, this, false);
AddView(_view);
_toolbar = (Toolbar)_view.FindViewById(Resource.Id.toolbar);
textureView = (AutoFitTextureView)_view.FindViewById(Resource.Id.textureview)
_questionTitleView = (Button)_view.FindViewById(Resource.Id.Start);
}
}
I tried using handler in .net maui but no luck with it.
My Maui Code is as follows
public interface ICameraRecordV3 : IView
{
public int StartTime { get; }
}
public partial class CameraRecordV3Handler
{
public static PropertyMapper<ICameraRecordV3, CameraRecordV3Handler> CustomMapper
= new PropertyMapper<ICameraRecordV3, CameraRecordV3Handler>(ViewHandler.ViewMapper)
{
[nameof(ICameraRecordV3.StartTime)] = MapStartTime,
};
public CameraRecordV3Handler() : base(CustomMapper)
{
}
public CameraRecordV3Handler(PropertyMapper mapper = null) : base(mapper ?? CustomMapper)
{
}
}
public class CameraRecordV3 : View, ICameraRecordV3
{
public static readonly BindableProperty StartProperty = BindableProperty.Create(
"StartTime", typeof(int), typeof(int), 6000);
public int Start
{
set { SetValue(StartTimeProperty, value); }
get { return (int)GetValue(StartTimeProperty); }
}
}
//Platform Specific code
public partial class CameraRecordV3Handler : ViewHandler<ICameraRecordV3, CameraRecordControl_V3>
{
private CameraRecordControl_V3 _cameraControl;
protected override CameraRecordControl_V3 CreatePlatformView()
{
_cameraControl = new CameraRecordControl_V3(Context, null);
return _cameraControl;
}
protected override void ConnectHandler(CameraRecordControl_V3 platformView)
{
base.ConnectHandler(platformView);
}
private static void MapStartTime(CameraRecordV3Handler handler, ICameraRecordV3 arg2)
{
handler.PlatformView?.UpdateStartTime(arg2.StartTime);
}
}
//MauiProgram
builder.ConfigureMauiHandlers(handlers =>
{
#if __ANDROID__
handlers.AddHandler(typeof(CameraRecordV3), typeof(iVue.Handlers.CameraRecordV3Handler));
#endif
});
You can continue to use CustomRenderer in MAUI, you just need to Remove any ExportRenderer directives as they won't be needed in .NET MAUI. And then configure each renderer using conditional compilation for each platform. You can replace handlers.AddCompatibilityRenderer with handlers.AddHandler in the documentation. Using handlers.AddCompatibilityRenderer will cause a crash.
I am trying to change back arrow image in navigation page. For this in android app i created navigation page renderer and then using method toolbar.SetNavigationIcon and its not working, but when i use toolbar.SetLogo its working fine.
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
toolbar.SetNavigationIcon(Resource.Drawable.arrow);
toolbar.SetLogo(Resource.Drawable.arrow);
}
public override void OnViewAdded(Android.Views.View child)
{
base.OnViewAdded(child);
if (child.GetType() == typeof(Android.Support.V7.Widget.Toolbar))
{
toolbar = (Android.Support.V7.Widget.Toolbar)child;
toolbar.ChildViewAdded += Toolbar_ChildViewAdded;
}
}
I also tried add image to app:navigationIcon in toolbar.axml, and it shows great in designer
my arrow
But, when i starting my app i have the same standart arrow icon
enter image description here
If your MainActivity is FormsApplicationActivity, you could refer to this example :
https://github.com/jessejiang0214/ChangeBackIconInXF/tree/master/Droid
If your MainActivity type is FormsAppCompatActivity, you could custom a PageRenderer and change the Toolbar's NavigationIcon.
For example :
[assembly: ExportRenderer(typeof(ContentPage), typeof(NavigationPageRendererDroid))]
...
public class NavigationPageRendererDroid : PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
var context = (Activity)Xamarin.Forms.Forms.Context;
var toolbar = context.FindViewById<Android.Support.V7.Widget.Toolbar>(Droid.Resource.Id.toolbar);
toolbar.NavigationIcon = Android.Support.V4.Content.ContextCompat.GetDrawable(context, Resource.Drawable.Back);
}
}
Usage :
MainPage = new NavigationPage(new MainPage());
...
//When click a button in MainPage, navigate to another page
private async void Button_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new TestPage());
}
Effect.
Update :
When you use Navigation.PushAsync() method navigate to another page, the system will automatically update the Toolbar's icon, you could find in the source code :
protected virtual Task<bool> OnPushAsync(Page view, bool animated)
{
return SwitchContentAsync(view, animated);
}
Task<bool> SwitchContentAsync(Page page, bool animated, bool removed = false, bool popToRoot = false)
{
...
UpdateToolbar();
...
}
void UpdateToolbar()
{
...
bool isNavigated = ((INavigationPageController)Element).StackDepth > 1;
if (isNavigated)
{
...
if (NavigationPage.GetHasBackButton(Element.CurrentPage))
{
//Set the navigation back icon < =================== !!! =-=
var icon = new DrawerArrowDrawable(activity.SupportActionBar.ThemedContext);
icon.Progress = 1;
bar.NavigationIcon = icon;
}
}
...
}
Solution :
So we have no choice but to custom a NavigationPageRenderer, override the OnPushAsync method to set the Toolbar's icon.
using AToolbar = Android.Support.V7.Widget.Toolbar;
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(NavigationPageRendererDroid))] // APPCOMP
...
public class NavigationPageRendererDroid : Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer // APPCOMP
{
public AToolbar toolbar;
public Activity context;
protected override Task<bool> OnPushAsync(Page view, bool animated)
{
var retVal = base.OnPushAsync(view, animated);
context = (Activity)Xamarin.Forms.Forms.Context;
toolbar = context.FindViewById<Android.Support.V7.Widget.Toolbar>(Droid.Resource.Id.toolbar);
if (toolbar != null)
{
if (toolbar.NavigationIcon != null)
{
toolbar.NavigationIcon = Android.Support.V4.Content.ContextCompat.GetDrawable(context, Resource.Drawable.Back);
//toolbar.SetNavigationIcon(Resource.Drawable.Back);
}
}
return retVal;
}
}
The CustomNavigationPage are defined in PCL :
public class CustomNavigationPage : NavigationPage
{
public CustomNavigationPage(Page startupPage) : base(startupPage)
{
}
}
Usage :
public App()
{
InitializeComponent();
MainPage = new CustomNavigationPage(new MainPage());
}
...
// In MainPage
private async void Button_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new TestPage());
}
I solved this the next way:
In my MainActivity i am added static toolbar property and identified it in OnCreateOptionsMenu
public static Toolbar ToolBar { get; private set; }
public override bool OnCreateOptionsMenu(IMenu menu)
{
ToolBar = FindViewById<Toolbar>(Resource.Id.toolbar);
ToolBar.SetNavigationIcon(Resource.Drawable.arrow);
return base.OnCreateOptionsMenu(menu);
}
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
....
}
Then in PageRenderer:
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
MainActivity.ToolBar?.SetNavigationIcon(Resource.Drawable.arrow);
}
But!! From 2 ways i have bad effect with redrawing
Usage:
async void tapImage_Tapped(object sender, EventArgs e)
{
await Navigation.PushAsync(new ChooseGenrePage(_listGenres));
}
I have my Class Adapter , and I need to have the access of two class ! Then I can put public class JSONAdapter extends ArrayAdapter<Voiture> { for have access in my class " Voiture " but I need to have the acceesss in my class "Moniteur" too , and I can"t put that :
public class JSONAdapter extends ArrayAdapter<Voiture>,ArrayAdapter<Moniteur> {
I need to view attributes of my class " Voiture " and " Moniteur " ...
Do you have the solution for me please ? Thanks
EDIT : Ok thanks you , this is the code of my class VOITURE :
public class Voiture {
private int idV = -1; // permet de voir si le parent est enregistré dans la BDD
private String marqueV;
private String dateAchatV;
private String PlaqueImmatriculationV;
public Voiture(String marqueV, String plaqueImmatriculationV) {
this.marqueV = marqueV;
PlaqueImmatriculationV = plaqueImmatriculationV;
}
public Voiture(JSONAdapter jsonAdapter) {
this.marqueV = marqueV;
this.PlaqueImmatriculationV = PlaqueImmatriculationV;
}
public int getIdV() {
return idV;
}
public void setIdV(int idV) {
this.idV = idV;
}
public String getMarqueV() {
return marqueV;
}
public void setMarqueV(String marqueV) {
this.marqueV = marqueV;
}
public String getDateAchatV() {
return dateAchatV;
}
public void setDateAchatV(String dateAchatV) {
this.dateAchatV = dateAchatV;
}
public String getPlaqueImmatriculationV() {
return PlaqueImmatriculationV;
}
public void setPlaqueImmatriculationV(String plaqueImmatriculationV) {
PlaqueImmatriculationV = plaqueImmatriculationV;
}
}
This is the code of my class Moniteur :
public class Moniteur {
private int idM;
private String nomM;
private String prenomM;
private String adresseM;
private String telephoneM;
public Moniteur(String nomM, String prenomM,String adresseM,String telephoneM) {
this.nomM = nomM;
this.prenomM = prenomM;
this.adresseM = adresseM;
this.telephoneM = telephoneM;
}
public int getIdM() { return idM; }
public void setIdM(int idM) { this.idM = idM; }
public String getNomM() {
return nomM;
}
public void setNomM(String nomM) {
this.nomM = nomM;
}
public String getPrenomM() {
return prenomM;
}
public void setPrenomM(String prenomM) {
this.prenomM = prenomM;
}
public String getAdresseM() {
return adresseM;
}
public void setAdresseM(String adresseM) {
this.adresseM = adresseM;
}
public String getTelephoneM() {
return telephoneM;
}
public void setTelephoneM(String telephoneM) {
this.telephoneM = telephoneM;
}
}
The purpose of these two classes and how they relate is not clear, but if you want to store both of them in a single container (which is what you are trying to do) you will need to define a common interface between them (or an abstract class). I don't speak French so you will have a easier time creating a good name.
I suggest creating an interface:
public interface AbstractItem
{
//TODO define common functions in this class
}
then implement this interface in your classes:
public class Voiture implements AbstractItem
{ ... }
public class Moniteur implements AbstractItem
{ ... }
Then, you can create an array adapter that will hold both of these items:
public class JSONAdapter extends ArrayAdapter<AbstractItem>
{ ... }
If I understand correctly, "Voiture" means "car" and "Moniteur" means "instructor" or "teacher".
So, it sounds like you are implementing a type of "driving school" where there are teachers with cars that they use/drive.
If that is the case, you really only need an ArrayAdapter<Monituer> and you could implement your Moniteur class like so.
public class Moniteur {
private List<Voiture> voitures;
public Monituer() {
voitures = new ArrayList<Voiture>();
}
public void ajouterVoiture(Voiture v) {
voitures.add(v);
}
public List<Voiture> obtientVoitures() {
return voitures;
}
}
Or maybe I don't understand what is trying to be displayed in the adapters. In that case, feel free to comment below.
Hey I am working on Xamarin and Mvvmcross, my issue is, when I start the app on (Samsung Galaxy Tab 3) Mdpi device then it show black screen and nothing happens,
here is my code,
Splash.cs
namespace Mobile.UI.Droid
{
[Activity(
Label = "Mobile Tasks"
, MainLauncher = true
, Icon = "#drawable/icon"
,Theme = "#style/MyActionBarSplash"
, NoHistory = true)]
public class SplashScreen : MvxSplashScreenActivity
{
public SplashScreen()
: base(Resource.Layout.SplashScreen)
{
}
}
}
setup.cs
public class Setup : MvxAndroidSetup
{
Context _context;
public Setup(Context applicationContext) : base(applicationContext)
{
_context = applicationContext;
CurrentPlatform.Init();
Insights.Initialize(XamarinInsightsConstants.APIKey, applicationContext);
}
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
var presenter = new CustomDroidViewPresenter();
Mvx.RegisterSingleton(presenter);
return presenter;
}
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
protected override System.Collections.Generic.IList<string> ViewNamespaces
{
get
{
var toReturn = base.ViewNamespaces;
toReturn.Add("MOBILE.Mobile.UI.Droid.Controls");
toReturn.Add("MOBILE.Mobile.UI.Droid.Utilities");
return toReturn;
}
}
protected override IMvxTrace CreateDebugTrace()
{
return new DebugTrace();
}
protected override void InitializeLastChance()
{
var errorHandler = new ErrorDisplayer(ApplicationContext);
//Cirrious.MvvmCross.Plugins.Color.PluginLoader.Instance.EnsureLoaded();
base.InitializeLastChance();
Mvx.RegisterSingleton<IDeviceDetails>(new CustomDroidDetails());
//Mvx.RegisterSingleton<ILogger>(new DroidLogger());
Mvx.RegisterSingleton<IVersionDetail>(new VersionDetail());
}
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterFactory(new MvxCustomBindingFactory<LinearLayout>("ShapeBackground", (view) => new ShapeBackgroundBinding(view)));
registry.RegisterFactory(new MvxCustomBindingFactory<TextView>("CustomText", (view) => new CustomTextBinding(view)));
}
}
VersionDetail.cs
public class VersionDetail : IVersionDetail
{
public string GetAppVersion()
{
var version = Application.Context.PackageManager.GetPackageInfo(Application.Context.PackageName, 0).VersionName;
return version;
}
}
CustomDroidDetails
public class CustomDroidDetails : IDeviceDetails
{
public Platform Platform
{
get { return Platform.Droid; }
}
public DeviceType DeviceType
{
get { return DeviceType.Phone; }
}
}
logcat
Error
I am being using mvvmcross to develop a cross platform app, it's helps us in most prominent way.
I am facing an minor issue on Loading indicator display.
I have a property named IsLoading in my BaseViewModel
public abstract class BaseViewModel : MvxViewModel
{
#region IsLoading
private bool _IsLoading;
public bool IsLoading
{
get { return _IsLoading; }
set
{
_IsLoading = value;
RaisePropertyChanged(() => IsLoading);
}
}
#endregion
}
Now, I have MainViewModel which create instance of two viewmodel inside it like below.
public class MainViewModel : BaseViewModel
{
public DashBoardViewModel DashboardVM
{
get { return _DashBoardVM; }
set
{
_DashBoardVM = value;
RaisePropertyChanged(() => DashboardVM);
}
}
public AccountViewModel AccountVM
{
get { return _AccountVM; }
set
{
_AccountVM = value;
RaisePropertyChanged(() => AccountVM);
}
}
public MainViewModel()
{
//To Do
}
public async Task Init()
{
DashboardVM = new DashBoardViewModel();
AccountVM = new AccountViewModel();
await Task.Delay(0);
}
}
Below is my DashBoardViewModel
public class DashBoardViewModel : BaseViewModel
{
public DashBoardViewModel()
{
IsLoading = true;
//Code to get data
}
}
As you can see in the above code i have setted the IsLoading = true, but at client end we don't get propertychanged event for this property.
Below is my one of client code to show progressdialog.
On Android we are using fragments, below is my MainView activity
public class MainView : MvxFragmentActivity
{
private ViewPager _viewPager;
private TabPageIndicator _pageIndicator;
private MvxViewPagerFragmentAdapter _adapter;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.MainView);
var fragments = new List<MvxViewPagerFragmentAdapter.FragmentInfo>
{
new MvxViewPagerFragmentAdapter.FragmentInfo
{
FragmentType = typeof(DashboardFragment),
Title = "",
ViewModel = ViewModel.DashboardVM
},
new MvxViewPagerFragmentAdapter.FragmentInfo
{
FragmentType = typeof(MyProfileFragment),
Title = "",
ViewModel = ViewModel.AccountVM
}
};
_viewPager = FindViewById<ViewPager>(Resource.Id.viewPager);
_adapter = new MvxViewPagerFragmentAdapter(this, SupportFragmentManager, fragments);
_viewPager.Adapter = _adapter;
_pageIndicator = FindViewById<TabPageIndicator>(Resource.Id.viewPagerIndicator);
_pageIndicator.SetViewPager(_viewPager);
_pageIndicator.CurrentItem = 0;
}
}
Below is my DashBoardFragment class
public class DashboardFragment : MvxFragment
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
var ignore = base.OnCreateView(inflater, container, savedInstanceState);
var viewToReturn = this.BindingInflate(Resource.Layout.Dashboard, null);
#region MVVMCROSS Binding
var set = this.CreateBindingSet<DashboardFragment, DashBoardViewModel>();
#endregion
#region Handle Progress bar
//initializing bindableprogress
var _bindableProgress = new BindableProgress(Activity);
//Binding with viewmodel property
set.Bind(_bindableProgress).For(m => m.IsLoading).To(vm => vm.IsLoading);
#endregion
#region Bind the set
set.Apply();
#endregion
return viewToReturn;
}
}
Note : BindableProgress is the class which has the property on which loading is been displayed and it is working fine on simple ViewModel.
Please help me to fix this.
Thanks
Aaman
I don't believe this is correct:
public class DashBoardViewModel : BaseViewModel
{
public DashBoardViewModel()
{
IsLoading = true;
//Code to get data
}
}
You probably don't want this happening in the constructor as you are creating the view models, not showing them yet. That is why you don't see an indicator because you set IsLoading to true but that is not the active screen.
Does something like this work?
public async Task Init()
{
IsLoading = true;
DashboardVM = new DashBoardViewModel();
AccountVM = new AccountViewModel();
await Task.Delay(0);
IsLoading = false;
}
The idea here being you should be showing the loading indicator for the MainView model during initialization of the child view models.