Ionic-native: Native plugins should be injectable (not using Static methods)

Created on 19 May 2016  路  16Comments  路  Source: ionic-team/ionic-native

Ran into this using the BLE plugin the other day:

The current usage of the BLE plugin (and most others, by the look of it) looks something like:

import {BLE} from 'ionic-native'

class DeviceService {
  connectToDeviceAndDoStuff(){
  //note the use of static method here
    return BLE.scan();
  }
} 

IMHO - this is easier at first glance than doing it with an injectable service (where the service is new'd by angular's injector and then you call _instance_ methods), but it quickly runs into some issues:

  • in an environment where BLE is not available (say, the browser during dev or in a PWA) there's no good way to use DI to provide an alternative implementation
  • ditto for testing - there's no reasonable way to provide a "MockBLE" service that you could trigger fake events etc for testing.

Proposed API might look something like this:

@Injectable()
class BLE {
  scan(){}
  connect(){} 
}

with which a user could then do

import {BLE} from 'ionic-native'

@Injectable()
class DeviceService {
  constructor(private ble:BLE){}
  connectToDeviceAndDoStuff(){
    this.ble.scan()...
  }
}

and in a test / browser / etc environment, I can easily do

provide(BLE, {useClass: MockBLEService});

A more comprehensive API thinking forward to usage in Browser based PWA type apps (think WebBluetooth and forthcoming native platform APIs like USB etc) might do something like this:

//abstract base class
abstract class BLEBackend {
  scan():Observable<Device>
}

//implementation for cordova
class CordovaBLEBackend implements BLEBackend {
  scan(){
    cordova.exec('BLE', 'scan', ....)
  }
}

//implementation for web bluetooth API
class WebBLEBackend {
  scan(){
    navigator.bluetooth.requestDevices(...)
  }
}

//injectable service consumers use

class BLE {
  constructor(private bleBackend: BLEBackend){}

  scan(){
    return this.bleBackend.scan();
  }
}

Which makes it nicely flexible - the public BLE API only depends on _some_ implementation of the backend being available, whether Cordova or WebBluetooth or w/e.

Thoughts?

discuss

Most helpful comment

Quick update: this is happening as we speak and going well. Will be part of the 3.x release

All 16 comments

Hey @robwormald ,

Thanks for the suggestion. It all makes sense and I see why you would say it works better as an injectable. But before going into details, what are your thoughts in regards to instance based plugins? (Example Media plugin)

Just a few concerns from the top of my head:

  • What if I would like to create multiple instances of Media plugin?
  • What if I need to get the URL for the file first, then instantiate the media plugin? (constructor requires media URL)
  • GoogleMaps SDK plugin needs an HTML container to be rendered in the DOM before we go ahead and create an instance of the plugin.

So overall, I think it's a good approach for static plugins but it's going to have conflicts with instance based ones.

@robwormald yours is definitely a better API approach. Mocking is really not easy with the current library, though Cordova handles browser fallbacks at a lower level so the current approach does gel with that (i.e. the browser platform target). Definitely worth revisiting how it's done currently, in the interest of simplicity the first go at it does nothing more than just statically wrap the cordova calls

Just my .02 I'd leave it as is and throw something in the docs as pertains to this topic, as my first thought was in fact to inject.

Had another thought: this is angular 2 specific which is fine in general but the project was meant to be agnostic. Perhaps an ng2 wrapper?

If that's the goal I've got a couple of ideas that would fulfill both. I'll send a PR on the BLE one and we can review

Any updates on this?

It's something we're exploring but haven't started on nor is it on the immediate roadmap. However, we do eventually want to do it.

After upgrading my apps to latest version of Ionic Framework 2, I noticed the changes to the way we display: Alert, ActionSheet, Loading, and Modal. We do not call a static function anymore to create an instance of the object, instead we inject the appropriate controller into our component and we create the object we need from there.

That process reminded me of this issue and I think we can apply the same concept here (works for instance based plugins too). Not sure how that would work out for Ionic 1 and non-Ionic users though. It seems like we would have to create a secondary wrapper that is explicitly for use with Angular 2 applications. Perhaps, we can automate it like @mlynch did over here: https://github.com/driftyco/ionic-native/blob/master/src/ng1.ts

This post here can be inspiring: http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2/

Yea I think wrappers for ng2 and ng1 can do this to expose it. That way we support it "natively" for the framework without depending on it?

Any news on this? I am having a lot of trouble trying to test native plugins as I have to make a wrapper for every plugin I am using in my project and this proves to be a big hassle. I would love if wrappers were provided out of the box to improve testability.

How are you testing currently? Open to ideas on how to do this well, as this is something on the list post 2.0 final of Ionic Framework which we're focused on right now

I am mocking the services by creating a mock class for each native service I am using inside my codebase that has all the public functions of the native wrapper class. Depending on the case I am then using jasmine spies to return mock data and thus simulate the native plugin's functionality. Not sure if this is what you were looking for in an answer

@masimplo - do you have an example you would like to share? Seems like it could be very useful to others.

@briebug I put a really silly gist example together really quick. I don't think that is more than anyone is doing already, but since you asked...
https://gist.github.com/masimplo/141746d1372f655d8b7016cdc336c716#file-native-keyboard-wrapper-ts-L7
My actual tests are much better than this and make this "pattern" really shine, but couldn't really copy paste from my actual project.
Downside is you have to wrap static calls, but coming from C# it feels normal for "third party code" to be wrapped in adapters.

Quick update: this is happening as we speak and going well. Will be part of the 3.x release

mmmm ok, but for moment what is the best way for create mocks in ionic-native?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lycwed picture lycwed  路  4Comments

mateo666 picture mateo666  路  3Comments

wwallace picture wwallace  路  4Comments

jgw96 picture jgw96  路  3Comments

GunaSekhar1 picture GunaSekhar1  路  4Comments