简体   繁体   中英

Xamarin Forms DependencyService with Prism with Autofac crash - Unhandled Exception

So to continue on with my first foray into Xamarin, I'm trying to develop an Content page that will take a photo, and then save the photo on the device gallery for viewing. I'm using Prism with Autofac and I'm following the wiki documentation on DependencyService and the examples that was provided on GitHub , but the program is crashing without explaining why.

I hate that!

So, here's my interface:

   public interface ISavePicture
{
    void SavePictureToGallery(string path);
}

ViewModel:

public class PluginPageViewModel : BindableBase
{
    private ISavePicture _savePicture;

    public PluginPageViewModel(ISavePicture savePicture)
    {
        try
        {
            TakePicCommand = new DelegateCommand(TakePicture);
            _savePicture = savePicture;
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);    
        }

    }

    public  ICommand TakePicCommand { get; private set; }

    private async void TakePicture()
    {
        try
            // Code here for getting the camera to take a picture ...

            _savePicture.SavePictureToGallery(filePath);
        }

        catch (Exception e)
        {
            Debug.WriteLine(e);
            throw;
        }

    }

  }
}

and the Android code:

using Android.App;
using Android.Content;
using Java.IO;
using RolodexDEMO_XF.Droid.Service;
using RolodexDEMO_XF.Services;
using Xamarin.Forms;
using Uri = Android.Net.Uri;

[assembly: Dependency(typeof(SavePicture_Android))]
namespace RolodexDEMO_XF.Droid.Service
{
    public class SavePicture_Android : Activity, ISavePicture
    {
        public void SavePictureToGallery(string path)
        {
            Intent mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
            var file = new File(path);
            Uri contentUri = Uri.FromFile(file);
            mediaScanIntent.SetData(contentUri);
            SendBroadcast(mediaScanIntent);
        }
    }
}

Notice that I DO have the assembly attribute for the DependencyService. I also wanted to note that I'm not using the emulator to test it out. Instead, I'm using my Galaxy Note 4 since I'm trying to test out the camera. For that part, I'm using Xamarin.Plugins from James Montemagno and that's working fine. I just can't save it, or see the pic if it is indeed saved to the device.

So where am I going wrong with it?

UPDATE: I was asked by others on what permissions I'm putting into my Android app, so in the AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto" package="RolodexDEMOXF.RolodexDEMOXF">
    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application android:theme="@style/MyTheme" android:label="Rolodex DEMO">
        <provider android:name="android.support.v4.content.FileProvider" android:authorities="RolodexDEMOXF.fileprovider" android:exported="false" android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data>
        </provider>
    </application>
</manifest>

and in the file_paths.xml (in the Resources\\xml directory)

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="Android/data/RolodexDEMOXF/files/Pictures" />
    <external-path name="my_movies" path="Android/data/RolodexDEMOXF/files/Movies" />
</paths>

I'm putting in the answer here just in case someone else has the same problem as I do in the future. Also, since there's little documentation, and I had to cobble together this alternative solution from a Chinese website (of all places!) and discussion with some of the guys on Slate Prism-Forms channel, I thought it would be best to put it out there on the alternative solution so at least you get an rudimentary idea on to resolve the DependencyService issues, and workarounds using Autofac DI.

I do want to note that there is discussion going on the Prism GitHub that Prism's implementation of DependencyService should be depreciated and go through this alternative that I'm describing here in this post. So hopefully one of the guys on the development team will document it and give better code examples on what I'm showing here.

That said, on with the show...

Okay, so I found out the answer to the problem. Long story short, the problem is the Autofac container.

And now the long winded version.

From what I gathered Dan Siegel, of all the containers that Prism can implement for a IoC/DI container, I had to pick the one that doesn't play well with others!

Why, do I say that it doesn't play well? According to the Autofac documentation, the container is Immutable which means it cannot be modified once it's been built. So my hypothesis is that when Prism goes through the project and tries to add in the registration types of DependencyService, Autofac is balking because the container is already built and therefore the "Unhandled Exception" is being thrown.

That explains the issue, but not the solution.

And what is the solution? Well, it turns out that Brian (Lagunas) and Brian (Noyes) have implemented a new interface called IPlatformInitializer, and I therefore have no choice but to use ContainerBuilder.Update(container) to add in the new RegisterType, which implements the additional RegisterTypes for the DependencyService, and I had to implement it like this:

public class AndroidInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainer container)
    {
        var temp = new ContainerBuilder();

        temp.RegisterType<SavePicture_Android>().As<ISavePicture>();
        temp.RegisterType<MessageService_Android>().As<IMessageService>();
        // ... add more RegisterTypes as needed ... 

        temp.Update(container);
    }
}

This class is already included in the Prism template project. For Android, it's in MainActivity.cs file.

Just in case you're wondering, it's the same for iOS and UWP. So instead of being AndroidInitializer:

  • iOSInitializer : IPlatformInitializer (iOS) (inside AppDelegate.cs)
  • UWPInitializer : IPlatformInitializer (UWP) (inside MainPage.xaml.cs)

On last thing: You can dump the [Assembly Dependency(typeof(X))] attribute since it is no longer needed. But you still need to do constructor dependency injection as they stated in their documentation for DependencyService.

As I said, the Prism gang is kicking around the idea of getting rid of their implementation of DependencyService on the next build of Prism and go down this route that I've explained to you.

It's also interesting to note is that the guys over Autofac are ALSO discussing on getting rid of the ContainerBuilder.Update() on the next version release of Autofac 4.

Just some fair warning, because of what I've put here may go out the window in the future.

I hope it helps someone out!

Various members of the Prism community are committed to making the Autofac implementation of Prism for Xamarin.Forms (Prism.Autofac.Forms) excellent; so using this IoC container option is not a bad choice. The issue has been that the first version of the Autofac implementation (for Prism.Forms) was written without the knowledge that the ContainerBuilder.Update() method was being deprecated; and that Autofac containers should be immutable (ie built once and not updated).

Also, the Autofac implementation does not have the built-in ability to resolve dependencies via the Xamarin.Forms DependencyService, if they cannot be resolved from the Autofac IoC container. This is a known issue and is not likely to be fixed, since - as Steve points out - the integration between Prism for Xamarin.Forms and the DependencyService is being re-considered (and possibly deprecated).

So, with Autofac, you just need to perform a secondary registration of your Xamarin.Forms dependency. You can do it as Steve showed - registering platform-specific implementations in the RegisterTypes() method of your IPlatformInitializer implementation; or you can use the Xamarin.Forms DependencyService to register it in the shared (PCL) project in the App.RegisterTypes() method (in your App.xaml.cs file).

Using the Xamarin.Forms DependencyService to register it in your shared project would look like this (again, in class that inherits from PrismApplication, which is generally the App.xaml.cs file):

protected override void RegisterTypes()
{
   var builder = new ContainerBuilder();

   builder.Register(ctx => Xamarin.Forms.DependencyService.Get<ISavePicture>())
      .As<ISavePicture>();

   builder.Update(Container);
}

Final note: The information provided above is correct for version 6.3.0 of Prism.Autofac.Forms. Since things are being re-worked (possibly for Prism 7.x) to remove the ability to update Autofac containers after they are built (ie make them immutable); it seems likely that at some point in the future, the code above would change to:

protected override void RegisterTypes()
{
   Container.Register(ctx => Xamarin.Forms.DependencyService.Get<ISavePicture>())
      .As<ISavePicture>();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM