NullReferenceException on DependencyService

EricSEricS Member ✭✭

I'm trying to implement Bluetooth on Android, but DependencyService.Get<IBluetooth>().Test() is throwing a NullReferenceException and I cannot figure out why. I think I'm doing everything right. My app is called MyApp. MyApp.Android references MyApp. I have a MyApp.iOS but it is only stubbed out and not set to compile.

MyApp\Home.xaml.cs:

var result = DependencyService.Get<IBluetooth>().Test(); //Debugger fails here, DependencyService.Get() is null.

MyApp\IBluetooth.cs:

namespace MyApp {
    public interface IBluetooth {
        bool Test();
    }
}

MyApp.Android\BluetoothAndroid.cs:

[assembly: Xamarin.Forms.Dependency(typeof(BluetoothAndroid))]
namespace MyApp.Droid {
    public class BluetoothAndroid : IBluetooth {
        bool Test() {
            BluetoothAdapter ba = BluetoothAdapter.DefaultAdapter;
            if (ba == null) return false;
            if (!ba.IsEnabled) return false;
            return true; //Incomplete method, just for testing
        }
    }
}

In my searching around I see lots of warnings to "register" the service but people either never say how or say to write this:

DependencyService.Register<SampleApp.Droid.Services.AddressBook>();

but do not say where that line of code goes. Also, I have not been able to determine if the [assembly] line is a way of registering the service.

My code was originally modeled after Stack Overflow answer /a/44126899/10122542 (I can't post links) about multi-platform toast notifications which actually did work for me briefly but has not in weeks and I cannot fathom why it doesn't. DependencyService.Get<IMessage>().ShortAlert("test") always yields a NullReferenceException!

Where is my code wrong? Thank you for your help!

Best Answer

  • EricSEricS ✭✭
    edited August 2018 Accepted Answer

    John, thanks. That's a really good point about testing for null for platforms not implemented.

    I figured out my problem. Under the file properties for BluetoothAndroid.cs, I had to set Build Action from Embedded Resource to Compile. I'm an idiot. The code in the initial question is just fine.

    My first clue was implementing what John wrote. BluetoothAndroid was not recognized. It was in the same project and namespace. My second clue was that nobody was really contesting my code, it was fundamentally correct.

    Clint's suggestion was okay but it was fundamentally not any different than what I had and seems to add an unnecessary layer. (Maybe that extra layer is helpful when the complexity of the application increases?)

    Thank you all for your help! You helped confirm that my code wasn't wrong, it was something else.

    For Google searchers coming here, the code in the original question does work. My problem was I had not set the .cs file to compile. You do not need to register anything in MainActivity, the [assembly] part is the registration.

    P.S. I also found MessageAndroid for displaying toasts was also not set to compile, so that's fixed now, too!

    P.P.S. I actually did tweak a few little things from the original question to get it to compile. Here's the final code:

    MyApp\Home.xaml.cs:

    var dsBT = DependencyService.Get<IBluetooth>();
    if (dsBT == null) return; //Warn user that this is not implemented on this platform.
    var result = dsBT.Test(); //This now works!
    

    MyApp\IBluetooth.cs:

    namespace MyApp {
        public interface IBluetooth {
            bool Test();
        }
    }
    

    MyApp.Android\BluetoothAndroid.cs:

    [assembly: Xamarin.Forms.Dependency(typeof(MyApp.Droid.BluetoothAndroid))]
    namespace MyApp.Droid {
        public class BluetoothAndroid : IBluetooth {
            public bool Test() {
                BluetoothAdapter ba = BluetoothAdapter.DefaultAdapter;
                if (ba == null) return false;
                if (!ba.IsEnabled) return false;
                return true; //Incomplete method, just for testing
            }
        }
    }
    

    P.P.P.S. I've got another problem now: BluetoothAdapter.DefaultAdapter is null. But at least that line of code is being executed now!

Answers

  • seanydaseanyda GBMember ✭✭✭✭✭

    It's worth a shot registering the service. You normally put it in AppDelegate.cs or MainActivity.cs before the LoadApplication().

    Here is an example that I use in my app:
    DependencyService.Register();

  • EricSEricS Member ✭✭

    No good, seanyda. I added this line in MyApp.Android\MainActivity.cs onCreate():

    Xamarin.Forms.DependencyService.Register<IBluetooth>();

    Now I don't get NullReferenceException but instead I get MissingMethodException: Default constructor not found for type MyApp.IBluetooth when the line in Home.xaml.cs is executed. I tried this before and abandoned it because (1) it makes no sense to add a default constructor to an interface, and (2) a Stack Overflow question (/q/38342298/10122542) addressed this but none of the answers helped me. I tried putting writing <BluetoothAndroid> but that didn't work; it wants an interface type. And, unlike your code, the compiler does not like when I put nothing.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Here's what I've always done. I don't go straight at the interface from the dependency service.

    In the shared project make the interface and a base class that derives from it.
    In the platform project make the implementation derive from the base class not the interface.

    shared project

        public interface IFileHelper
        {
            //Define all your method signatures
        }
    
        public class FileHelperBase : IFileHelper
        {
           //Implement base level of all those signatures
    

    Android project

    [assembly: Dependency(typeof(FileAccessHelper))]
    namespace MyApp.Droid
    {
        public class FileAccessHelper : FileHelperBase
        {
            #region Constructors
            public FileAccessHelper()
            {
              //Have to have a parameterless constructor
            }
            #endregion Constructors
    
           // All the platform-specific implimentations that couldn't be done in the shared-base class
    }
    
  • JohnHardmanJohnHardman GBUniversity mod
    edited August 2018

    I think the post above by @seandya fell victim to the markup/markdown stuff.
    The post should have shown:

    DependencyService.Register<BluetoothAndroid>();
    

    That assumes that BluetoothAndroid implements the IBluetooth interface.

    BTW - as a matter of practice, I would recommend always checking the result of DependencyService.Get for null before using it. When developing cross-platform apps, you inevitably will have occasions when you have implemented a dependency service for some platforms but not others.

  • EricSEricS Member ✭✭
    edited August 2018 Accepted Answer

    John, thanks. That's a really good point about testing for null for platforms not implemented.

    I figured out my problem. Under the file properties for BluetoothAndroid.cs, I had to set Build Action from Embedded Resource to Compile. I'm an idiot. The code in the initial question is just fine.

    My first clue was implementing what John wrote. BluetoothAndroid was not recognized. It was in the same project and namespace. My second clue was that nobody was really contesting my code, it was fundamentally correct.

    Clint's suggestion was okay but it was fundamentally not any different than what I had and seems to add an unnecessary layer. (Maybe that extra layer is helpful when the complexity of the application increases?)

    Thank you all for your help! You helped confirm that my code wasn't wrong, it was something else.

    For Google searchers coming here, the code in the original question does work. My problem was I had not set the .cs file to compile. You do not need to register anything in MainActivity, the [assembly] part is the registration.

    P.S. I also found MessageAndroid for displaying toasts was also not set to compile, so that's fixed now, too!

    P.P.S. I actually did tweak a few little things from the original question to get it to compile. Here's the final code:

    MyApp\Home.xaml.cs:

    var dsBT = DependencyService.Get<IBluetooth>();
    if (dsBT == null) return; //Warn user that this is not implemented on this platform.
    var result = dsBT.Test(); //This now works!
    

    MyApp\IBluetooth.cs:

    namespace MyApp {
        public interface IBluetooth {
            bool Test();
        }
    }
    

    MyApp.Android\BluetoothAndroid.cs:

    [assembly: Xamarin.Forms.Dependency(typeof(MyApp.Droid.BluetoothAndroid))]
    namespace MyApp.Droid {
        public class BluetoothAndroid : IBluetooth {
            public bool Test() {
                BluetoothAdapter ba = BluetoothAdapter.DefaultAdapter;
                if (ba == null) return false;
                if (!ba.IsEnabled) return false;
                return true; //Incomplete method, just for testing
            }
        }
    }
    

    P.P.P.S. I've got another problem now: BluetoothAdapter.DefaultAdapter is null. But at least that line of code is being executed now!

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    @EricS said:
    Clint's suggestion was okay but it was fundamentally not any different than what I had and seems to add an unnecessary layer. (Maybe that extra layer is helpful when the complexity of the application increases?)

    Its more so that 90% of the functionality that can work agnosticly is in the shared layer and thus the base class so it isn't duplicated in each of the platform implementations. SortByDate for example... Sort is sort... No need to have identical code in two places when you know one will get updated a year from now and the other probably forgotten. Just keeping that whole object inheritance concept in play.

  • JohnHardmanJohnHardman GBUniversity mod
    edited August 2018

    I find that about a quarter of my dependency services end up with a base class that is used across 2-N platforms. I am working on one now that shares code between Android and iOS, but not UWP. In the days when I was supporting WinPhone, WinRT and UWP, I used to often share code between the various Windows projects, but not with Android and iOS. I don't put in the base classes on the off chance I need them later on, but I definitely do use them to avoid code duplication.

    [Edit: Just had a look at that part of my codebase - actually it's not a quarter, more like 60%]

  • EricSEricS Member ✭✭

    That makes a lot of sense, Clint & John. I'll likely refactor to do that. Thanks!

Sign In or Register to comment.