Custom MapBox Android Renderer - crash when change tabs - xamarin.forms

I am trying to create a MapBox Renderer for Android in Xamarin Forms using the naxam library.
The map is displaying well as a content of a view in a tab, however when I change tab several times it crashes on the android emulator. I suppose the problem comes from the renderer but can't figure out where.
Here is a part of my code :
public class MapViewRenderer : ViewRenderer<ContentView, Android.Views.View>,
IOnMapReadyCallback, Com.Mapbox.Mapboxsdk.Maps.Style.IOnStyleLoaded
{
private MapView _mapView = null;
private MapboxMap _mapBox = null;
public MapViewRenderer(Context context) : base(context)
{
Mapbox.GetInstance(Context, "pk.###");
}
public void OnMapReady(MapboxMap p0)
{
_mapBox = p0;
[...]
}
public void OnStyleLoaded(Com.Mapbox.Mapboxsdk.Maps.Style p0)
{
[...]
}
protected override void OnElementChanged(ElementChangedEventArgs<ContentView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
_mapView = new MapView(Context);
_mapView.GetMapAsync(this);
view.Content = _mapView.ToView();
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_mapView.Dispose();
}
base.Dispose(disposing);
}
}
All the examples I read on the net are about creating the MapView in the main activity with OnCreate, OnStart, OnResume... I haven't found any about creating a map in a custom render.
Please help.
EDIT :
---------------------------------SOLUTION---------------------------------
The problem was fixed using the code below in the custom renderer. In addition the renderer uses a Mapview instance which has been moved inside the main activity following ToolmakerSteve's remark.
MapViewRenderer.cs :
public class MapViewRenderer : ViewRenderer<ContentView, Android.Views.View>
{
public MapViewRenderer(Context context) : base(context)
{}
protected override void OnElementChanged(ElementChangedEventArgs<ContentView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var view = e.NewElement as Views.Map;
if (Control == null)
{
SetNativeControl(MainActivity.MainActivityInstance.MapView);
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
MainActivity.MainActivityInstance.MapView.RemoveFromParent();
}
base.Dispose(disposing);
}
}
MainActivity.cs :
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity, IOnMapReadyCallback, Style.IOnStyleLoaded
{
public MapView MapView { get; private set; } = null;
public MapboxMap MapboxMap { get; private set; } = null;
public static MainActivity MainActivityInstance { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
[...]
MainActivityInstance = this;
Mapbox.GetInstance(this, "pk.###");
MapView = new MapView(this);
MapView.GetMapAsync(this);
MapView.OnCreate(savedInstanceState);
}
protected override void OnStart()
{
base.OnStart();
MapView.OnStart();
[...]
}
protected override void OnResume()
{
base.OnResume();
MapView.OnResume();
}
protected override void OnPause()
{
MapView.OnPause();
base.OnPause();
}
protected override void OnSaveInstanceState(Bundle outState)
{
base.OnSaveInstanceState(outState);
MapView.OnSaveInstanceState(outState);
}
protected override void OnStop()
{
base.OnStop();
MapView.OnStop();
}
protected override void OnDestroy()
{
MapView.OnDestroy();
base.OnDestroy();
}
public override void OnLowMemory()
{
base.OnLowMemory();
MapView.OnLowMemory();
}
public void OnMapReady(MapboxMap p0)
{
MapboxMap = p0;
[...]
}
public void OnStyleLoaded(Com.Mapbox.Mapboxsdk.Maps.Style p0)
{
[...]
}
}

Wrap the contents of each method in try-catch, and write to Debug output any exception (hopefully instead of crashing).
You will probably need to find out exactly what causes the crash, for anyone to help you.
Do this by adding Debug.WriteLine statements throughout the custom renderer.
I also recommend verifying that it really is the custom renderer that causes the problem.
Do this by commenting out most of the custom code. So that it displays as an empty view.
You need to add code to the MainActivity methods. This won't fix this crash, but it will avoid crashing later, for example when app goes into background.
Naxam library install guide says:
The MapView contains its own lifecycle methods for managing Android's OpenGL lifecycle, which must be called directly from the containing Activity. In order for your app to correctly call the MapView's lifecycle methods, you must override the following lifecycle methods in the Activity that contains the MapView and call the respective MapView method.
Android code from that link:
// Set this from your custom renderer.
public MapView mapView;
override fun onStart() {
super.onStart()
mapView?.onStart()
}
override fun onStop() {
super.onStop()
mapView?.onStop()
}
override fun onLowMemory() {
super.onLowMemory()
mapView?.onLowMemory()
}
override fun onDestroy() {
super.onDestroy()
mapView?.onDestroy()
}
Translate that into C# equivalent.
In your custom renderer, set the activities mapView variable:
Xamarin Essentials.Platform.CurrentActivity.mapView = ...;
You should also set that variable to null:
in OnElementChanged / e.newElement == null
in Dispose.
In my experience, on Android, OpenGL views don't like to be hidden - which is what happens when switching to different tab. (MapBox uses an OpenGL view.)
Unfortunately the result is a "hard crash" - won't be caught by try/catch. Won't give useful information about why it crashed.
If this is the cause, none of the above will fix the problem.
You might have to do custom logic that disposes the map when the tab is hidden, recreates it when the tab comes back.
See https://stackoverflow.com/a/52186885/199364, for the event you need to add code to.

Related

Find when a page finished loading in xamarin.forms prism

I am trying to create an optimized Event Tracker for my Xamarin.forms prism application. I would like to know whether there is any method to find out when the page load is finished. If someone could help me, please save my time.
You can implement page loading methods for different platforms through custom renderer.
For iOS, override the viewDidLoad method, ViewDidLoad(Called after the controller's view is loaded into memory.)
[assembly:ExportRenderer (typeof(ContentPage), typeof(MyRenderer))]
namespace Demo.iOS
{
public class MyRenderer : PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
}
}
}
For Android, rewrite the OnAttachedToWindow method, OnAttachedToWindow(This is called when the view is attached to a window.)
[assembly: ExportRenderer(typeof(ContentPage), typeof(MyRenderer))]
namespace Demo.Droid
{
public class MyRenderer : PageRenderer
{
public MyPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
}
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
}
}
}

Can't find method Shiny.IShinyStartup.ConfigureServices with Prism 8

The error I'm getting during app startup is the following:
Parent class vtable failed to initialize, due to: Could not load list of method overrides due to Method not found: void Shiny.IShinyStartup.ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection) assembly:/Users/merickson/Library/Developer/CoreSimulator/Devices/D19E269D-A3E5-46C2-BB9C-94A122EA02DC/data/Containers/Bundle/Application/F9A34529-8F64-4868-8D7F-389C77DB54BC/PrismSizeTest.iOS.app/Shiny.Prism.dll type:PrismStartup member:(null)
I'm not sure if I'm missing a NuGet, maybe wrong version of one or more NuGets, or am I initializing something wrong? Any help is appreciated.
I've been able to replicate this with a bare bones solution which I can provide if necessary. Here are the main files involved:
App.xaml.cs
public partial class App : PrismApplicationBase
{
public App(IPlatformInitializer initializer)
: base(initializer)
{
}
protected override async void OnInitialized()
{
InitializeComponent();
await NavigationService.NavigateAsync("NavigationPage/MainPage");
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<IAppInfo, AppInfoImplementation>();
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();
}
protected override IContainerExtension CreateContainerExtension() => ContainerLocator.Current;
}
AppDelegate.cs
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
ShinyHost.Init(new ApplePlatform(), new MyStartup());
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App(new iOSInitializer()));
return base.FinishedLaunching(app, options);
}
}
public class iOSInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
// Register any platform specific implementations
}
}
MyStartup.cs
public class MyStartup : PrismStartup
{
protected override void ConfigureServices(IServiceCollection services)
{
services.UseGps<GpsDelegate>();
}
}
NuGets installed:
There is nothing directly wrong with the code... however the latest versions of Shiny have breaking ABI changes that make Shiny.Prism and Shiny.Core incompatible which is the result of the issue shown here...
For more information you can track the issue at: https://github.com/dansiegel/Prism.Container.Extensions/issues/183

Xamarin Forms:Prism MVVM:Android:Navigate to specific page when click on Push Notification [duplicate]

I'm new to Xamarin and Prism, so forgive me if I'm missing something obvious. I am following the example of this project in GitHub. However, I am getting the following error when running my Android project.
System.InvalidOperationException: The current type, MyApp.Abstractions.IFacebookManager, is an interface and cannot be constructed. Are you missing a type mapping?
The error is occurring in EntryPage.xaml.g.cs in the LoadFromXaml() method.
[global::Xamarin.Forms.Xaml.XamlFilePathAttribute("Views\\EntryPage.xaml")]
public partial class EntryPage : global::Xamarin.Forms.ContentPage {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "2.0.0.0")]
private void InitializeComponent() {
// Exception thrown here!
global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(EntryPage));
}
}
This leads me to believe that I'm not using the IoC container properly, but I can't see what I'm doing differently from the example. The EntryPageViewModel's constructor takes IFacebookManager as a parameter and I understand that Unity should take care of this. I have a breakpoint on the constructor, but it's not being hit. This is a .NET Standard Xamarin solution. I would appreciate any help and guidance. Thank you!
Here's my App.xaml.cs
using MyApp.Abstractions;
using MyApp.Helpers;
using MyApp.Services;
using MyApp.ViewModels;
using MyApp.Views;
using Prism;
using Prism.Ioc;
using Prism.Unity;
using Xamarin.Forms;
namespace MyApp
{
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
ServiceResolver.Instance.Add<ICloudService, AzureCloudService>();
NavigationService.NavigateAsync("NavigationPage/EntryPage");
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<EntryPage, EntryPageViewModel>();
}
}
}
Here's my MainActivity.cs
using Android.App;
using Android.Content.PM;
using Android.OS;
using MyApp.Droid.Services;
using MyApp.Abstractions;
using Android.Content;
using Xamarin.Facebook;
using Xamarin.Forms;
using Prism;
using Prism.Ioc;
namespace MyApp.Droid
{
[Activity(Label = "MyApp", Icon = "#drawable/icon", Theme = "#style/MainTheme", Name = "com.mydomain.myapp.MainActivity", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
ICallbackManager fbCallbackManager;
AndroidLoginProvider loginProvider;
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();
global::Xamarin.Forms.Forms.Init(this, bundle);
DependencyService.Register<IFacebookManager, FacebookManager>();
LoadApplication(new App(new AndroidInitializer()));
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
var manager = DependencyService.Get<IFacebookManager>();
if (manager != null)
{
(manager as FacebookManager)._callbackManager.OnActivityResult(requestCode, (int)resultCode, data);
}
}
}
public class AndroidInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
}
public class AndroidInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
You are mixing the Xamarin Forms Dependency Service with the one provided with Prism.Unity
Instead of calling DependencyService.Register<IFacebookManager, FacebookManager>();
You need to register it with the AndroidInitializer .
public class AndroidInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry container)
{
container.RegisterSingleton<IFacebookManager, FacebookManager>();
}
}
You can then always resolve a dependency manually by calling App.Container.Resolve inside OnActivityResult.
To complete the previous answer that helped me a lot to fix a similar issue:
with prism 7, you need to resolve a dependency via IContainerProvider as follow:
var container = (App.Current as Prism.Unity.PrismApplication).Container;
var manager = container.Resolve<IFacebookManager>();
In this case, Unity is used as the IoC "container"

React-native inside a Fragment

How to Start react-native inside of a fragment?
While putting react-native inside Fragment, onCreateView function is unable to return View from mReactRootView.
View view = inflater.inflate(mReactRootView. , container, false);
I've managed to figure this out with much trial and error. I've seen this question asked around the internet and thought that this was the best place to post the answer. Here is how to do with the latest version of React (0.29 as of this writing):
The first thing we'll do is create an abstract ReactFragment class that we will use throughout our app:
public abstract class ReactFragment extends Fragment {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
// This method returns the name of our top-level component to show
public abstract String getMainComponentName();
#Override
public void onAttach(Context context) {
super.onAttach(context);
mReactRootView = new ReactRootView(context);
mReactInstanceManager =
((MyApplication) getActivity().getApplication())
.getReactNativeHost()
.getReactInstanceManager();
}
#Override
public ReactRootView onCreateView(LayoutInflater inflater, ViewGroup group, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
return mReactRootView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mReactRootView.startReactApplication(
mReactInstanceManager,
getMainComponentName(),
null
);
}
}
We'll now be able to create fragments that render React Native components, e.g.:
public class HelloFragment extends ReactFragment {
#Override
public String getMainComponentName() {
return "hellocomponent"; // name of our React Native component we've registered
}
}
A little more work is required, though. Our parent Activity needs to pass some things into the ReactInstanceManager in order for the React Native lifecycle to work properly. Here's what I ended up with:
public class FragmentActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
/*
* Get the ReactInstanceManager, AKA the bridge between JS and Android
* We use a singleton here so we can reuse the instance throughout our app
* instead of constantly re-instantiating and re-downloading the bundle
*/
private ReactInstanceManager mReactInstanceManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
/**
* Get the reference to the ReactInstanceManager
*/
mReactInstanceManager =
((MyApplication) getApplication()).getReactNativeHost().getReactInstanceManager();
/*
* We can instantiate a fragment to show for Activity programmatically,
* or using the layout XML files.
* This doesn't necessarily have to be a ReactFragment, any Fragment type will do.
*/
Fragment viewFragment = new HelloFragment();
getFragmentManager().beginTransaction().add(R.id.container, viewFragment).commit();
}
#Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
/*
* Any activity that uses the ReactFragment or ReactActivty
* Needs to call onHostPause() on the ReactInstanceManager
*/
#Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause();
}
}
/*
* Same as onPause - need to call onHostResume
* on our ReactInstanceManager
*/
#Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
#Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
return super.onKeyUp(keyCode, event);
}
}
Finally, you'll notice the reference to (MyApplication) throughout the code; this is a global Application object to contain our ReactInstanceManager, AKA the bridge between Android and React Native. This is the pattern that the React Native projects use internally, so I simply copied it. Here's how it's implemented:
public class MyApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
#Override
public boolean getUseDeveloperSupport() {
return true;
}
#Override
public List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
};
#Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
}
The trickiest bit was figuring out the lifecycle between the Fragment and the Activity; the ReactRootView needs a reference to the Activity context in order to instantiate, so making sure that getActivity() would not be null was important. Also, registering the onHostPause() and onHostResume() in the parent Activity was unintuitive at first, but ultimately proved simpler once the ReactNativeInstanceManager was abstracted away into a global instead of keeping it on the Activity or Fragment.
Hope this helps someone else out there!
There are libraries available that handle this for you.
One that I use is react-native-android-fragment
As per the instructions on the linked GitHub repository:
Add the following line to your build.gradle compile 'com.github.hudl:react-native-android-fragment:v0.43.2'.
e.g.
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
// Version will correspond to its dependnecy on React Native
compile 'com.github.hudl:react-native-android-fragment:v0.43.2'
}
Build you react code into the fragment
Fragment reactFragment = new ReactFragment.Builder()
.setComponentName("HelloWorld")
.setLaunchOptions(launchOptions) // A Bundle of launch options
.build();
Place the Fragment in a FrameLayout that you would have in your XML layout file. In my case, the FrameLayout ID is react_holder.
getSupportFragmentManager()
.beginTransaction()
.add(R.id.react_holder, reactFragment)
.commit();
There is now an official ReactFragment available here that can be used to host react native inside a fragment.
Just make sure you have your react native host setup correctly, as the fragment tries to access the react native host on the application level, or overload it in a subclass:
// inside the ReactFragment
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getActivity().getApplication()).getReactNativeHost();
}
you can then create the fragment using:
val reactNativeProcessFragment = ReactFragment.Builder()
.setComponentName("nameUsedIn_AppRegistry.registerComponent")
.build()

Add action to a cancel button in an editable grid in vaadin

I'm working with an editable Grid with Vaadin 7. When a row is in edit mode, it shows two buttons: save and cancel.
(Just in case, the image was taken from here Book of Vaadin)
With the:
grid.getEditorFieldGroup().addCommitHandler(new CommitHandler() {
private static final long serialVersionUID = 1L;
#SuppressWarnings("unchecked")
#Override
public void preCommit(CommitEvent commitEvent) throws CommitException{}
#Override
public void postCommit(CommitEvent commitEvent) throws CommitException{}
});
I can do something in the save action.
But, can I do something like that with the cancel action?
Thank you.
This is a serious deficiency of the component. According to the forum, they're working on it, but for the time being it seems that the most straightforward way is to extend the Grid component and override the doCancelEditor method. Here's a snippet:
public class MyGrid extends Grid {
protected Object newRow;
#Override
protected void doCancelEditor() {
super.doCancelEditor();
getContainerDataSource().removeItem(newRow);
setEditorEnabled(false);
}
public void setNewRow(Object newRow) {
this.newRow = newRow;
}
Note that you have to tell the MyGrid object when you create the row. Also, note that you're extending the server side, so you don't have to alter the client (widget code), but you do need to refer to the new component in your UI design.
Actually, saveEditor() should be also overridden, as doCancelEditor() seems to be invoked on save action, too. My code:
public class MyGrid extends Grid {
private boolean addingMode = false;
private JPAContainer<SomeEntity> container;
private Object recentlyAddedItemID;
public MyGrid(Indexed indexed) {
container = indexed;
}
#Override
protected void doCancelEditor() {
Object id = getEditedItemId();
super.doCancelEditor();
if (addingMode) {
getContainerDataSource().removeItem(id);
recentlyAddedItemID = null;
}
addingMode = false;
}
#Override
public void saveEditor() throws FieldGroup.CommitException {
if (addingMode) recentlyAddedItemID = getEditedItemId();
addingMode = false;
super.saveEditor();
}
public Object getRecentlyAddedItemID() {
return recentlyAddedItemID;
}
public void addNewElement(SomeEntity entity) {
addingMode = true;
editItem(container.addEntity(entity));
}
}
MyGrid grid = new MyGrid(JPAContainerFactory.make(SomeEntity.class, entityManager));
grid.addNewElement(new SomeEntity());
/*
if we want to know the new items's ID (actually the new primary key
in case of JPAContainer), we can check it by:
*/
Object id = grid.getRecentlyAddedItemID();
/*
returns null if editor was cancelled and finally nothing new was persisted
*/

Resources