⚠️ This lesson is retired and might contain outdated information.

Develop a consistent plugin api across your Android and iOS NativeScript app

Nathan Walker
InstructorNathan Walker
Share this video with your friends

Social Share Links

Send Tweet
Published 8 years ago
Updated 9 months ago

One of the exciting benefits you have with NativeScript is the ability to write native code (Java and Objective C) directly in JavaScript/TypeScript allowing you to create a consistent high level api over top of both underlying iOS Objective C/Swift and Android Java apis. In this lesson, we will look at how to design a consistent api that taps into the appropriate native api’s on the target platforms using underlying native libraries.

[00:00] We have an iOS library using a CocoaPod and we have an Android library compiling a plug-in from Gradle. We've developed two APIs here for Android and iOS. We don't care what platform it's running on, we just know we can call the Filestack API and it will automatically handle the right platform under the hood.

[00:19] We want to unify the API, and it's good to find common denominators between the two. Right away we'll notice a glaring difference. Our Android API we had extended observable, and in our iOS class we had not.

[00:32] It would be good to create a common super class that both extends observable that both of these classes could use as a superclass, that way whether it's iOS or Android, the both emit events in the same way. Let's start with just that.

[00:44] We'll create a Filestack.common.typescript file, so we'll call this just common, and we'll have it extent NativeScript's observable class. Then we'll instead import this common class and use that as our base superclass on both Android and iOS, and we'll have this extends common, and we'll make sure that our constructor calls super.

[01:09] Right now, the constructor creates this Filestack file picker and presents it in the view. This is not a very useful API for the usage of this plug-in, because we'd like to be able to call up when we want this to appear.

[01:21] Let's refactor this into a method we could call open, and in fact we could define a common interface that both of these classes would implement. We'll call this iFilestack, and we'll just make sure that there is an open method, and it would be nice if this open method returned a promise when has been called.

[01:40] We'll type this as promise, and any for right now. Now we can import and just let our class implement this interface. Immediately it tells us that we need to implement the open method, so we can start to refactor this now. For iOS we're going to take these lines here and just refactor them down into open.

[01:59] Our open method returns a promise, so we'll wrap this in a new promise. This gives us a good opportunity to actually implement the completion parameter of this Objective-C call. We can just pass our resolve method here, since completion is called once this view controller presents the file picker controller.

[02:19] Let's configure the same API on the Android side, so we're going to implement this iFilestack interface, and we need to implement the open method, and it should return a promise. Inside here, we can just go ahead and resolve this promise after the activity has been started.

[02:36] Since we wire up this global Android event every time open is called, we want to make sure that this is torn down after we actually get a result here. Here, we'll just call app.android and we'll just unbind this event.

[02:52] This will make sure it's cleaned up every time. Then we want to handle this result the same, between Android and iOS. Let's actually use our notification system from NativeScript's observable class, and let's notify out a common event between both platforms.

[03:07] It would be nice to use a constant enumerable here, so let's exports a const called FSEvents, and that will be equal to an object that we will type as iFSEvents, and to start off we'll just add one for when the files were added, we could call it addedFiles, and we'll type this, and we'll implement that here.

[03:30] Now we can port that, and then we'll use it her. Our event name is going to be FSEventsAddedFiles, the object is this class, and the data here is going to be this files collection. We'll always hand back a collection of files.

[03:46] We'll do the same for iOS, and our delegate is the one that actually handles the files for iOS, and you can see we pass that back to a method called addedFiles on this owner class here, so we can actually just notify from here.

[04:01] Let's import our event, and set the event name here to FSEventsAddedFiles, the object is this class, and the data here is going to be a collection of files which we haven't implemented here yet, so let's add that.

[04:17] We'll rename our parameters similar to what we had for Android, and we'll push in the file here, and we'll make sure our files collection gets sent out. One thing that we should be aware of, often with native SDKs the models that are used to represent some of these data structures often do not have the exact same properties or getters.

[04:38] Let's develop a way to inspect this model, and in particular this File model that is coming from these SDKs, to see what their property differences are. Let's go into our common class here, and let's implement a method called inspect, this is going to take an object of any type, and we're going to print out some useful things to the console to allow us to actually look at this object.

[05:00] We want to definitely look at each key on the object and the value of those keys. Now we can use that on both platforms as a utility. Instead of just logging out the this.rawfile, let's actually inspect this, and we'll do the same on iOS in our iterator here.

[05:16] This will help us design a common model that can extrapolate the differences between the property keys on Android and iOS, and give us a constant standard model object to deal with these files.

[05:28] Now with this in place, let's rebuild our plug-in to get the new typings, but let's modify our build script again, and we want the iOS and Android suffix, but now we want to also include this common suffix as well. This will build the proper files for our internal plug-in. Let's build it, and now let's update the typings for our index.

[05:46] If we look at Android definitions and iOS definitions, there's slight differences, mainly because we're using an addedFiles with our delegate on iOS. Let's take the Android definitions this time, and use these as our main index. Since we haven't implemented upload local on iOS yet, we'll omit that for now, and just work with our new open method which returns a promise.

[06:08] Now let's use it, let's add a button to our view, and we'll have the label say choose file, and we'll wire this to a new tap event called openPicker. Let's implement this, and we'll want to reuse our FileStack reference, so let's make this a private reference on our component.

[06:25] We can now use our API. This will call up the picker, and for now we'll log that it was opened. Now we can also wire up our event, and we can grab that from FSEvents. This is where having those previously defined really helps.

[06:42] This will be our event, and for now let's just log that they were received in the component. If you were to run this now, you would get a hard crash telling you this FSEventsAddedFIles is not an object.

[06:54] This is in fact because the common classes that we exported here are not included when our plug-in gets added. As we can see in our package, we specify a main as FileStack, but by default NativeScript will only grab the .android and .ios suffix when it builds the particular platforms.

[07:12] Therefore, we need a way to make sure our common code gets compiled in. Let's clean things up a bit to solve this. First let's create a folder, and we'll call it source. Inside source we'll create an android folder, and an iOS folder.

[07:25] Instead we're going to drop these suffixed files inside their corresponding folder, and we'll drop common at the root of our source file. Now what we can do is actually get rid of these suffixes, since we will rely on our folder structure instead, and we'll rename our common file to just simply common.

[07:42] Now at the root of our internal plug-in folder we'll create filestack.ios and filestack.android. For Android, we're just going to export everything from our source folder android filestack, then this time we want to also make sure that we grab everything from common.

[07:58] We're going to do the same thing inside of the iOS file, but this time we're just going to export everything from our iOS source file. This way our main key still specifies the appropriate file name, and then all of our code from common and our platform specific code gets built.

[08:13] Lastly, we don't need common as a suffix for our build script anymore. Now let's build and run that on iOS. Now, we've designed in a lot more control over our native library's API.

[08:24] When we choose file, we get the opened resolve back from our component, and if we upload a file, we get back the inspection of that object where we can see clearly the keys and values. Let's hold those off to the side for a minute so we can compare them to Android.

[08:42] On Android if we choose file, you can see we get the same promise resolution in our component, and let's choose a file, and let's just pick this for example. We can see filesAdded in component, so we got our event, but we get all the keys and properties printed from this model.

[08:59] We can see it does not have the same property names as iOS. iOS use urlFileNameSize, and this has getFileName, getSize, getURL. We want to create a common model t handle this. Lastly we'll go into the common source for our plug-in, and we're going to export an interface that defines a common set of properties.

[09:21] Let's add a method to our common class, and get the correct properties whether it is iOS or not. For iOS we'll just extrapolate those exact properties. For Android, we'll implement what is supported, and make those that are not optional.

[09:34] Lastly, we'll implement and use this method here, and here in Android. Now you've provided a consistent way to access your Android and iOS libraries.