I cannot figure out how to get a Mapbox map going in a custom view renderer on Android using Xamarin.Forms. It's driving me bonkers.
In my PCL, I have a map view.
public class MapView: View
{
public MapView() { }
}
For iOS, the "getting started" help was close enough to get it working on iOS, like so:
[assembly: ExportRenderer (typeof(Shared.Mobile.MapView), typeof(MapViewRenderer))]
namespace Clients.iOS
{
public class MapViewRenderer : ViewRenderer<Shared.Mobile.MapView, UIView>
{
protected override void OnElementChanged(ElementChangedEventArgs<Shared.Mobile.MapView> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
{
return;
}
var uiView = new UIView(new CoreGraphics.CGRect(0, 0, 500, 700));
SetNativeControl(uiView);
var mapView = new MapView(Control.Bounds);
mapView.SetCenterCoordinate(new CoreLocation.CLLocationCoordinate2D(40.81, -96.68), false);
mapView.SetZoomLevel(11, false);
mapView.AddAnnotation(new PointAnnotation
{
Coordinate = new CoreLocation.CLLocationCoordinate2D(40.81, -96.68),
Title = "Lincoln, NE",
Subtitle = "What-what"
});
uiView.AddSubview(mapView);
}
}
}
On the Android side, not so much. (https://components.xamarin.com/gettingstarted/mapboxsdk). They're putting in XML and an Activity of sorts, but my knowledge in mobile doesn't extend far from Xamarin.Forms at the moment, so I can't seem to bridge the gap between the two. My Android renderer looks like this:
public class MapViewRenderer : ViewRenderer<Shared.Mobile.MapView, Android.Views.View>
{
protected override void OnElementChanged(ElementChangedEventArgs<Shared.Mobile.MapView> e)
{
base.OnElementChanged(e);
var view = new Android.Views.View(Context);
SetNativeControl(view); // NullReferenceException will be thrown if the native control is not set
if (Control == null)
{
return;
}
var mapView = new MapView(Forms.Context, "thisismyaccesscode");
mapView.CenterCoordinate = new LatLng(41.885, -87.679);
mapView.ZoomLevel = 11;
mapView.SetMinimumHeight(250);
mapView.SetMinimumWidth(250);
mapView.AddMarker(new MarkerOptions().SetPosition(new LatLng(40.81, -96.68)).SetTitle("Lincoln, NE"));
view.AddSubview(mapView) // I wish this method existed
}
}
My final call to AddSubview(mapView) is not in fact a method of the View class as it is the UIView class on iOS. Here's where I'm stuck. I cannot figure out how to display the MapView. Please help.
As you have already mentioned you can't call AddSubview as it is an iOS method.
On Android its the equivalent of AddView.
However - You are attempting to do this type of operation on a Android View object and not on a ViewGroup object, so its not possible.
First instead of doing:-
var view = new Android.Views.View(Context);
try instantiating the MapView directly such like:-
var view = new MapView(Context, "thisismyaccesscode");
Your SetNativeControl call on the view is fine.
I haven't tried the Mapbox component, so I'm unclear on the exact parameter types it is expecting in the code above.
Should that not work, however, then do something like the following:-
var view = new Android.Widget.FrameLayout(Context);
var mapView = new MapView(Forms.Context, "thisismyaccesscode");
mapView.CenterCoordinate = new LatLng(41.885, -87.679);
mapView.ZoomLevel = 11;
mapView.SetMinimumHeight(250);
mapView.SetMinimumWidth(250);
mapView.AddMarker(new MarkerOptions().SetPosition(new LatLng(40.81, -96.68)).SetTitle("Lincoln, NE"));
view.AddView(mapView);
SetNativeControl(view);
You will have to change your first line to the following also:-
public class MapViewRenderer : ViewRenderer<Shared.Mobile.MapView, Android.Widget.FrameLayout>
Related
I have a Xamarin Forms application that I am porting from a FormsActivity to an AppCompatActivity. For functional requirements, the hamburger icon should always show, which I have done by using
NavigationPage.SetHasBackButton(nextPage, false);
But this does not work with AppCompat and the issue report has sat unaddressed for over a year, so I'm trying to get around the issue with a custom renderer. I can successfully create the icon and display it, but tapping on it does not display the correct behavior. I'm thinking what I need to do is set the DrawerIndicatorEnabled to true and call SyncState, but I need a reference to the ActionBarDrawerToggle to do that and I haven't figured out how to get it.
Edit: This is the bug report with screen shots:
https://github.com/xamarin/Xamarin.Forms/issues/2577
Here is the custom NavigationPageRenderer:
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace HeaderTest.Droid
{
public class CustomNavigationPageRenderer : NavigationPageRenderer // APPCOMP
{
public CustomNavigationPageRenderer(Context context) : base(context)
{
}
protected override Task<bool> OnPushAsync(Page view, bool animated)
{
var retVal = base.OnPushAsync(view, false);
var context = (Activity)Context;
var toolbar = context.FindViewById<AToolbar>(Resource.Id.toolbar);
if (toolbar != null)
{
var icon = new DrawerArrowDrawable(context)
{
SpinEnabled = false,
};
toolbar.NavigationIcon = icon;
}
return retVal;
}
}
}
I am working with xamarin.forms and I am using a picker.
But, depend of the selected value, on android it breaks the line...But I need it always use only one line. How can I do that?
You can create a custom renderer for the Picker as the comment suggested, and then in your renderer, you can for example code like this:
[assembly: ExportRenderer(typeof(MyPicker), typeof(MyPickerRenderer))]
namespace NameSpace.Droid
{
public class MyPickerRenderer : PickerRenderer
{
private bool isShown;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var et = this.Control as EditText;
et.SetSingleLine(true);
}
}
}
}
I've recently started using MvvmCross 5 and using the new INavigationService.
However, I've been unable to find any new ways of presenting Dialog Fragments so I'm still using my old method in a custom presenter (MvxFragmentsPresenter) as follows:
protected override void ShowFragment(MvxViewModelRequest request)
{
var currentActivity = Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity;
var appActivity = currentActivity as MvvmCross.Droid.FullFragging.Views.MvxActivity;
if (appActivity != null)
{
var loaderService = Mvx.Resolve<IMvxViewModelLoader>();
var viewModel = loaderService.LoadViewModel(request, null);
if (request.ViewModelType == typeof(ExampleViewModel))
{
var dialogFragment = new ExampleDialogFragment();
dialogFragment.ViewModel = (ExampleViewModel)viewModel;
dialogFragment.SetStyle(DialogFragmentStyle.Normal, Resource.Style.MainDialogTheme);
dialogFragment.Show(appActivity.FragmentManager, ExampleDialogFragment.DialogTag);
return;
}
}
The problem I'm having is when I need to pass parameters into this dialog fragment, the navigation service creates the view model with the correct parameters but when it goes to show the fragment, the custom presenter then constructs a new view model without any parameters and sets it to the new dialog fragment.
I have tried using MvxDefaultViewModelLocator instead of the Loader but this just does the same thing.
var locatorService = Mvx.Resolve<IMvxViewModelLocator>();
var viewModel = locatorService.Load(request.ViewModelType, new MvxBundle(request.ParameterValues), null);
Is there a new way to open dialog fragments so the view model that's created with parameters can be assigned to it?
Thanks for any pointers.
We added built-in support for Dialogs in https://github.com/MvvmCross/MvvmCross/pull/2099 This is currently available on Myget and will be released as part of the next MvvmCross release. We are also working on making a more permanent fix for multiple viewmodel instances.
I'm trying to set page icon. There is App class of PCL below.
public App()
{
CustomMainPage mainPage = new CustomMainPage();
NavigationPage rootPage = new NavigationPage(mainPage);
this.MainPage = rootPage;
}
What I tried to do?
NavigationPage.SetTitleIcon(mainPage, "icon.png");
The second approach.
NavigationPage.SetTitleIcon(this, "icon.png");
The third approach.
NavigationPage.SetTitleIcon(rootPage, "icon.png");
File icon.png is situated into Resources/drawable.
And finally I decided to implement my custom renderer for the NavigationPage in Xamarin Forms. NavigationPage is setted to MainPage property into App class of PCL.
I created DroidNavigationRenderer class into Android project.
[assembly: ExportRenderer(typeof(NavigationPage), typeof(DroidNavigationPageRenderer))]
namespace App1.Droid.DroidRenderers
{
public class DroidNavigationPageRenderer : NavigationPageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
var actionBar = ((Activity)Context).ActionBar;
actionBar.SetIcon(Resource.Drawable.icon);
}
}
}
But actionBar is always returns as null.
What I do wrong and how to fix it?
In your ContentPage, you have the Icon property.
You can set it this way
Icon = Device.OnPlatform("Menu", "ic_fa_bars.png", "Assets/Icons/reload.png");
What is your CustomMainPage class? If I have TabbedPage and set one of its child I do it this way:
var navigationPage = new NavigationPage(mainPage);
if (Device.RuntimePlatform == Device.iOS)
{
navigationPage.Icon = "services.png";
}
hope it helps
I have a weird / unique situation with my ListView. This is the scenario:
I'm making use of the MVP design pattern. As the Activity starts, it raises an event to notify the presenter to fetch some data from a web service. The web service call is an Async call. Once the web service Completed event is raised, I take the result and push it into a property (which is of type Array) that resides within my View / Activity.
Everything I mentioned works just fine, but as soon as the device is rotated, some interesting developments take place.
The async call resumes as normal and provides the property (Array) with a value. So nothing wrong there... (And yes there is data in the collection) I then set the ListView Adapter and call the notifyDataSetChanged, but nothing happens. The UI is not updated or anything?? If I re-enter the Activity the data is visible again ??
I even tried calling invalidateViews and invalidate on the ListView - this didn't do anything.
Could someone please assist me in this matter?
Many thanks in advance!
[Update]
I would like to stress the fact that I am making use of C# (Xamarin) and not Java (:sigh: - yes I know). Furthermore, I am not making use of the ASyncTask class, instead I'm making use of the async methods created within the proxy classes generated by Visual Studio. Pretty straight forward, but this is the code that populates the ListView - the property is set from the presenter
Presenter
Where View is of type IContactsView
protected override void OnCollectData(System.Collections.IEnumerable data, Type typeOfData)
{
if (data != null && typeOfData != null && typeOfData.Equals(typeof(UserContact)))
{
this.View.UserInformationCollection = data.Cast<UserContact>().ToArray();
}
}
Activity
The activity implements IContactsView
public UserContact[] UserInformationCollection
{
get
{
return this._userInformationCollection;
}
set
{
this.RunOnUiThread(() =>
{
this._userInformationCollection = value;
ListView listview = this.FindViewById<ListView>(Resource.Id.userLV);
if (listview != null)
{
UserContact[] subsidiesList = this.GetIndexedContacts(this._userInformationCollection);
listview.Adapter = new ContactsAdapter(this, subsidiesList.ToList());
((ContactsAdapter)listview.Adapter).NotifyDataSetChanged();
}
});
}
}
[/Update]
Found a much better solution! So please ignore the static variable idea!
Activity:
Override the OnRetainNonConfigurationInstance and return the presenter
public override Java.Lang.Object OnRetainNonConfigurationInstance()
{
return this._presenter;
}
Within the OnCreate check the LastNonConfigurationInstance and get the presenter - if it isn't null:
protected override void OnCreate(Bundle bundle)
{
...
if (this.LastNonConfigurationInstance != null)
{
this._presenter = this.LastNonConfigurationInstance as ContactsPresenter;
this._presenter.RefreshView(this);
}
else
{
// create a new presenter
this._presenter = new ContactsPresenter(this);
}
...
}
So maybe, you saw what I did in the previous code sample? Yes, I send the new instance of the activity to the presenter - have a look at the RefreshView
Presenter:
So within my base presenter I have the following method:
public class Presenter<T> : Java.Lang.Object, IPresenter where T : IView
{
/// <param name="view">The view.</param>
public void RefreshView(T view)
{
this.View = view;
}
}
The above code helps my presenter say with the creation of new activities - so when it returns data after the async call it will have the latest and greatest instance of the activity!
Hope this helps!
Kind regards,
Got it working by doing the following:
declare a static variable of the activity:
private static ContactsActivity _cachedActivity = null;
Overrode the OnResume within the activity and set the variable:
protected override void OnResume()
{
base.OnResume();
_cachedActivity = this;
}
Override the OnCreate within the activity and set the variable:
protected override void OnCreate(Bundle bundle)
{
...
_cachedActivity = this;
...
}
Lastly I changed the property mentioned earlier:
public USBUserContact[] UserInformationCollection
{
get
{
return this._userInformationCollection;
}
set
{
_cachedActivity.RunOnUiThread(() =>
{
_cachedActivity._userInformationCollection = value;
ListView listview = _cachedActivity.FindViewById<ListView>(Resource.Id.userLV);
if (listview != null)
{
UserContact[] subsidiesList = _cachedActivity.GetIndexedContacts(_cachedActivity._userInformationCollection);
listview.Adapter = new ContactsAdapter(_cachedActivity, subsidiesList.ToList());
((ContactsAdapter)listview.Adapter).NotifyDataSetChanged();
}
});
}
}
Kind regards,