danielsogl / awesome-cordova-plugins

Native features for mobile apps built with Cordova/PhoneGap and open web technologies. Complete with TypeScript support.
https://danielsogl.gitbook.io/awesome-cordova-plugins/
Other
2.41k stars 2.42k forks source link

Discussion: support mixed native/web modes and beyond Angular #1930

Closed mlynch closed 3 years ago

mlynch commented 7 years ago

Right now Ionic Native is a bit cumbersome to use for apps that are deployed both to the app store and the web (PWA, etc.).

Ideally, Ionic Native works transparently for plugins with existing implementations on native and the web. Things like Geolocation, Camera, etc.

For ones that don't, we need to make it easy to detect that case and code around it, and also give developers the option to mock data.

Finally, Ionic is moving away from relying on any single frontend framework (like Angular), and instead moving to standard web components and vanilla JS APIs. The current injectable dependence makes that hard.

A Proposal

To support Web APIs, given that cordova browser is probably not a workable solution for Ionic PWAs/web stuff, we will have a web implementation for each plugin that has one. To the user, this will be seamless.

To move away from classes and injectables, it might be nice to move to a functional approach, so a plugin like Geolocation would look like this (pseudocode):


import { pluginFunction } from `@ionic-native/core`;

const normalizeResult = (position) => {
  // normalize native API response and web API response
  return position;
}

const normalizeError = (err) => {
  // normalize native API and web API errors
  return err;
}

const webGetCurrentPosition = () => {
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition((position) => {
      resolve(normalizeResult(position));
    }, (err) => {
      reject(normalizeError(err));
    });
  })
};

const getCurrentPosition =  pluginFunction('geolocation', 'getCurrentPosition', webGetCurrentPosition);

export { getCurrentPosition };

And then pluginFunction contains the logic for proxying to Cordova/Native layer (might be different on electron, for example), as well as making it easy to provide default data or a callback to handle when Cordova isn't available but there's no web API available:

const plugin = (value) => {
  // return some kind of type (promise/observable/etc.)
}

const pluginFunction = (pluginId, method, webFallback) => {
  return (defaults) => {
     // If cordova/native layer is available, try to call it
     if(cordova) { //.... }
     // If not, call web fallback if available
     if(webFallback) { return pluginResult(webFallback()) }
     // Otherwise, return the defaults provided for mocking purposes
     return pluginResult(defaults)
  }
};

export { pluginFunction };

Consider this the start of a discussion, would love thoughts and ideas.

cc @danbucholtz @ihadeed @jgw96 @mhartington

mhartington commented 7 years ago

Something to consider are plugins who's API is a 1:1 match for the web API.
Geolocation being one of them, but things like the new media stream plugin

https://github.com/phonegap/phonegap-plugin-media-stream

There's a big push in the plugins to make them follow the spec, I think we should first audit which core plugins follow the web standard and take them into consideration.

mlynch commented 7 years ago

@mhartington one question that leaves is how to "activate" those. I think we decided we didn't want to use the cordova browser target, so how else would we load these?

mhartington commented 7 years ago

So the code you'd write would be a match for the web API. For example the image capture plugin

https://github.com/phonegap/phonegap-plugin-image-capture

        navigator.mediaDevices.getUserMedia({
            'audio': true,
            'video': {
                facingMode: 'user'
            }
        }).then(function(getmedia) {
            var track = getmedia.getVideoTracks()[0];
            var imageCapture = new ImageCapture(track);
        });

This code matches the actual browser implementation.

https://developer.mozilla.org/en-US/docs/Web/API/MediaStream

So even without cordova, the API will still work, because it's just standard JS

mlynch commented 7 years ago

That seems like a nice side effect. What about ones that don't have web analogs? What is the calling convention? That's where I start to question this approach.

danbucholtz commented 7 years ago

I have been kicking around the idea of using stencil components for parts of Ionic Native. Since Stencil components are lazy loaded by default, we can get away with some pretty neat platform-specific things without introducing a lot of code maintenance/overhead.

One of my goals of Ionic Native has always been to support multiple platforms with one API. Namely, cordova, traditional web, and electron with one API. That way, I truly can write once and deploy anywhere.

For the sake of discussion, let's consider the writeFile API.

If we wanted to support multiple platforms and only load the code actually used for the platform, we could do something like this:

@Component({
    tag: 'ion-file-api'
})
export class IonFileAPI implements FileAPI {

    @Method writefile(filePath: string, fileContent) {
        return writeFileImpl(getPlatform(), filePath, fileContent);
    }
}

writeFileImpl(platform: string, filePath: string, fileContent: string) {
    const tagName = `${platform}-file-api`;
    let element = document.querySelector(tagName);
    if (!element) {
        // it is doesn't exist, insert it
        element = document.createElement(`${platform}-file-api`); // <web-file-api>, <electron-file-api>, <cordova-file-api>
        document.body.appendChild(element);
    }
    // isReady is a utility function that ensures an element is fully hydrated and ready to go before resolving a promise
    return isReady(element).then((fileApi) => {
        return fileApi.writeFile(filePath, fileContent);
    });
}

The ion-file-api is a component that could be added to the document.body behind the scenes or directly. This would open the door to using the file api, which is already async by nature.

In the writeFileImpl method, we append a node dynamically. This node would either be web-file-api, electron-file-api, or cordova-file-api for the sake of argument. When this node hits the DOM, it loads the platform specific code needed to execute and perform the API call.

I could then have two stencil components that look like this:

@Component({
    tag: 'web-file-api'
})
export class WebFileApi implements FileAPI {

    @Method writeFile(filePath: string, fileContent: string) {
        // write to local storage
    }
}
@Component({
    tag: 'cordova-file-api'
})
export class CordovaFileApi implements FileAPI {

    @Method writeFile(filePath: string, fileContent: string) {
        // use cordova api
    }
}

Where one is used for web, and the other is used for Cordova.

So what is really happening here? Well, first of all, when the ion-file-api node hits the dom, it begins lazy loading it's own writeFile implementation. That writeFile implementation doesn't actually write a file, it just loads another DOM node, and delegate the writing of the file to it. Since everything is async already, the user gets one promise and they're none-the-wiser.

From a developer experience perspective, developer's don't even need to know anything about the DOM or that Ionic native uses it at all.

They could write code like this:

import { File } from '@ionic-native/file';

export class SomeAngularComponent {

    ionViewDidEnter() {
        File.writeFile('myFilePath.txt', 'someContent');
    }    
}

In this example, when File.writeFile is called, it appends the ion-file-api to the DOM, which in turns appends the worker nodes to the DOM.

I think this approach makes a ton of sense because the developer experience is great, and this would enable us to support multiple platforms with one code base, without adding unnecessary size and complexity to a user's app.

Thanks, Dan

kinggolf commented 7 years ago

Will further development of Ionic on Angular end someday, soon? My Ionic apps have benefitted from ongoing improvements in Angular and Ionic, but now I am wondering if this is coming to or is at the end.

If I am starting new Ionic apps, phone apps not PWA's, should I be converting to web components strategy?

I appreciate all the great work from Ionic team.

jpike88 commented 6 years ago

Is there a way to easily disable Ionic Native so it can be packaged in my web deployments without having a hissy fit?