Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better documentation for extending native classes #141

Open
2 tasks done
pianocomposer321 opened this issue Apr 17, 2024 · 1 comment
Open
2 tasks done

Better documentation for extending native classes #141

pianocomposer321 opened this issue Apr 17, 2024 · 1 comment

Comments

@pianocomposer321
Copy link

Is your feature request related to a problem? Please describe.

I need to have access to a native method for the ListView component. The documentation is very unclear about how I would go about doing this. It says that I can subclass android components and has a short example of what this would look like, but the example is frankly very unhelpful:

let constructorCalled = false

@NativeClass
class MyButton extends android.widget.Button {
  constructor() {
    super()
    constructorCalled = true

    // necessary when extending TypeScript constructors
    return global.__native(this)
  }

  setEnabled(enabled: boolean): void {
    this.super.setEnabled(enabled)
  }
}

const button = new MyButton(context)

This example refers to the NativeClass decorator and the android.widget.Button class. Presumeable these would have to be imported from some nativescript modules, but there are no import statements at the top of the file. Where are these coming from? Also, how do I then use this class after extending the base class? If I want it to show up in a stack layout on my main page, for example, how would I do this using nativescript? What about with Vue? Or Svelte? Just instantiating it at the bottom of the file does nothing to tell the application where it should be placed. Also, do I need to place this code in some specific file or directory for it to be picked up by the rest of my code, like from the .xml file?

None of this is mentioned.

Describe the solution you'd like

I'd be satisfied if someone could answer my questions here, but I think the documentation could benefit a lot from this information being added to it directly.

Describe alternatives you've considered

I've searched through the docs, and searched the web too. Nothing is helpful.

Anything else?

No response

Please accept these terms

@rigor789
Copy link
Member

rigor789 commented Apr 17, 2024

Good questions, I'm moving this into the docs repo. I'll try to answer the questions here, since a proper docs page will require more than a few bullet points...

  1. NativeClass is globally available, no import needed, @nativescript/core provides it. It's technically a no-op decorator that gets stripped out during bundling, it's used to signal to webpack/typescript (with a custom transformer) that this class should be converted to es5 syntax in the bundle, as the runtime understands es6 syntax fine, but when extending native classes, the mechanisms used only work on es5 classes (the v8 javascript engine doesn't expose the necessary hooks when using es6).

  2. android.widget.Button doesn't need to be imported either, because those are directly mapped to their native counterparts/java namespaces. For example the following java code (from a 3rd party/plugin/App_Resources - doesn't matter)

    package foo.bar.baz;
    
    public class MyClass {}

    Would allow you to access it from your javascript through it's fully classified name (meaning, namespace + class name):

    const myClassInstance = new foo.bar.baz.MyClass();

    no imports needed - it's a "java" global.

  3. For widgets and whatnot, you would subclass View and implement the createNativeView method, for a very simple example, let's do a Button widget with a custom subclassed android.widget.Button:

    // example.ts
    import { View } from "@nativescript/core";
    
    @NativeClass
    class MyButton extends android.widget.Button {
      constructor() {
        super()
    
        // necessary when extending TypeScript constructors
        return global.__native(this)
      }
    
      setEnabled(enabled: boolean): void {
        this.super.setEnabled(enabled)
      }
    }
    
    
    export class MyNativeScriptButtonView extends View {
       createNativeView() {
         return new MyButton();
       }
    }

    There's a few ideas in this example worth mentioning:

    • MyButton will be a proper java class, but it's namespace will be "random", this means any java method that expects a android.widget.Button (or a View instance) will happily accept this as a parameter.
    • If you need to, you can add the @JavaProxy('foo.bar.baz.MyButton'); decorator along with the @NativeClass decorator to make sure the namespace is not "random". This way you can pass it into stuff like AndroidManifest.xml and whatnot (not a great example in case of a widget, but let's say you subclass the Activity, then you'd want to declare it in the manifest, this is where the JavaProxy decorator helps).
    • MyNativeScriptButtonView is a NativeScript wrapper around the MyButton native widget, this makes it work with the nativescript view hierarchy, css etc.
  4. Lastly, to render/show this MyNativeScriptButtonView:

    • In Vue, you need to register it: Vue.registerElement('MyNativeScriptButtonView', () => MyNativeScriptButtonView);. Then you use it like any other view in your template: <MyNativeScriptButtonView />
    • Same applies for Angular, Svelte, React - all generally have a registerElement function that tells the renderer what that view/element is.
    • Core xml you would define it in the xml itself, for example (I can't remember how to define global tags, as I don't really use the core/xml myself)
      <GridLayout xmlns:mynamespace="./example">
       <mynamespace:MyNativeScriptButtonView />
      </GridLayout>

There's a lot to unpack, but once you understand the basics, it's fairly straight forward to follow. I always recommend reading code in @nativescript/core itself, as all the built-in views are implemented this way. The above is definitely a reduced example, as it doesn't really touch on the property system in core, css properties etc.

For reference, see the Android implementation of the default <Button> element: https://github.com/NativeScript/NativeScript/blob/main/packages/core/ui/button/index.android.ts

@rigor789 rigor789 transferred this issue from NativeScript/NativeScript Apr 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants