Better documentation for extending native classes
pianocomposer321 opened this issue · 1 comments
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
- I have searched the existing issues as well as StackOverflow and this has not been posted before
- I agree to follow this project's Code of Conduct
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...
-
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). -
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.
-
For widgets and whatnot, you would subclass
View
and implement thecreateNativeView
method, for a very simple example, let's do a Button widget with a custom subclassedandroid.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 aandroid.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 theJavaProxy
decorator helps). MyNativeScriptButtonView
is a NativeScript wrapper around theMyButton
native widget, this makes it work with the nativescript view hierarchy, css etc.
-
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>
- In Vue, you need to register it:
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