Skip to content

Commit 5cde054

Browse files
omfgnutsandsonjosef
authored andcommitted
Added new Eduardo's Jetpack Article
1 parent 36aceb6 commit 5cde054

4 files changed

Lines changed: 392 additions & 0 deletions

File tree

41.5 KB
Loading
Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
= Introduction to Jetpack Compose for NativeScript
2+
3+
Building user interfaces declaratively is something the Web community has widely adopted, and nowadays, large applications are built following these principles. For example, Google launched Jetpack Compose, and Apple announced SwiftUI at WWDC19, receiving an immensely positive response from developers.
4+
5+
Here at Valor Software, we are always excited about new advancements in development technologies, and we are fans of NativeScript. We collaborated with nStudio to provide an effective and enjoyable Jetpack Compose integration for Android apps driven by NativeScript.
6+
7+
Earlier this month we announced https://dev.to/valorsoftware/introduction-to-swiftui-for-nativescript-4m1b[SwiftUI for NativeScript, window=_blank], which follow the same principles and API design as Jetpack Compose for NativeScript.
8+
9+
In this article, we'll demonstrate how to use Jetpack Compose within NativeScript to explore fun new possibilities in building amazing UIs together.
10+
11+
== Create a NativeScript app
12+
13+
We can create an app using a standard TypeScript template:
14+
15+
[, bash]
16+
----
17+
ns create jetpackcompose --ts
18+
cd jetpackcompose
19+
----
20+
21+
This will setup what is often called a "vanilla" flavored NativeScript app. You can use whichever flavor you're most comfortable with, though. Setting the plugin up for Angular (and most other flavors) is usually a case of registering the view, which we'll demonstrate in a section below.
22+
23+
== Install the Jetpack Compose plugin:
24+
25+
[, bash]
26+
----
27+
npm install @nativescript/jetpack-compose
28+
----
29+
30+
NOTE: Jetpack Compose requires you to use at least API 21 (Lollipop) as your minimum SDK version. You can do this by adding `minSdkVersion 21` to your app.gradle.
31+
32+
If you plan to build your libraries directly from Android Studio, you don't need anything else, just drop your built `.aar` in `App_Resources/Android/libs/` and skip to the next section. But if you're planning on writing Kotlin code directly in `.kt` files in `App_Resources/Android/src/main/java`, then we need some extra steps.
33+
34+
First, add your compose dependencies in `app.gradle`:
35+
36+
[, json]
37+
----
38+
dependencies {
39+
def compose_version = "1.2.1"
40+
implementation "androidx.compose.ui:ui:$compose_version"
41+
// Tooling support (Previews, etc.)
42+
implementation "androidx.compose.ui:ui-tooling:$compose_version"
43+
44+
// Add any other dependencies your Jetpack Compose UI needs
45+
// like material design:
46+
// implementation 'androidx.compose.material:material:$compose_version'
47+
}
48+
----
49+
50+
Then modify the `android` section so you enable compose:
51+
52+
[, json]
53+
----
54+
android {
55+
// other settings like targetSdk, etc.
56+
57+
buildFeatures {
58+
compose true
59+
}
60+
compileOptions {
61+
sourceCompatibility JavaVersion.VERSION_1_8
62+
targetCompatibility JavaVersion.VERSION_1_8
63+
}
64+
kotlinOptions {
65+
jvmTarget = "1.8"
66+
}
67+
composeOptions {
68+
kotlinCompilerExtensionVersion '1.3.2'
69+
}
70+
}
71+
----
72+
73+
And finally, enable Kotlin by creating the file `App_Resources/Android/gradle.properties`
74+
75+
[, json ]
76+
----
77+
useKotlin=true
78+
kotlinVersion=1.7.20 # you can choose your kotlin version here
79+
----
80+
81+
== Jetpack Compose usage
82+
83+
A. Create your Jetpack Compose views and wrapper
84+
+
85+
Create `App_Resources/Android/src/main/java/BasicView.kt`:
86+
+
87+
[, java]
88+
----
89+
package com.example
90+
91+
import android.content.Context
92+
import androidx.compose.material.MaterialTheme
93+
import androidx.compose.material.Text
94+
import androidx.compose.runtime.Composable
95+
import androidx.compose.runtime.getValue
96+
import androidx.compose.runtime.mutableStateOf
97+
import androidx.compose.runtime.setValue
98+
import androidx.compose.ui.platform.ComposeView
99+
import androidx.lifecycle.ViewModel
100+
import androidx.lifecycle.viewmodel.compose.viewModel
101+
102+
class BasicView {
103+
fun generateComposeView(view: ComposeView): ComposeView {
104+
return view.apply {
105+
setContent {
106+
MaterialTheme {
107+
Text("Hello from Jetpack Compose")
108+
}
109+
}
110+
}
111+
}
112+
113+
fun updateData(value: Map<Any, Any>) {
114+
}
115+
var onEvent: ((String) -> Unit)? = null
116+
117+
}
118+
----
119+
+
120+
To use the default plugin handling of Compose views, it's important that your implementation follows the following interface:
121+
+
122+
[, java]
123+
----
124+
class Example {
125+
fun generateComposeView(view: ComposeView): ComposeView {
126+
// render your compose views into the ComposeView
127+
}
128+
129+
fun updateData(value: Map<Any, Any>) {
130+
// this function receives data from NativeScript
131+
// value is a js object converted to a map
132+
}
133+
134+
// this is the event you will send back to Jetpack Compose
135+
// when you need to pass data, just call onEvent?.invoke(v)
136+
var onEvent: ((Any) -> Unit)? = null
137+
138+
}
139+
----
140+
141+
B. Register your Jetpack Compose via the `composeId`
142+
+
143+
This can be done in the NativeScript app's bootstrap file (often `app.ts` or `main.ts`).
144+
+
145+
[, js]
146+
----
147+
import { registerJetpackCompose, ComposeDataDriver } from '@nativescript/jetpack-compose';
148+
149+
// A. You can generate types for your own Compose Provider with 'ns typings android --aar {path/to/{name}.aar}'
150+
// B. Otherwise you can ignore by declaring the package resolution path you know you provided
151+
declare var com;
152+
registerJetpackCompose('sampleView', (view) => new ComposeDataDriver(new com.example.BasicView(), view));
153+
----
154+
+
155+
Additionally, if you want to use Angular, you can register the compose view itself:
156+
+
157+
[, js]
158+
----
159+
import { registerElement } from '@nativescript/angular';
160+
import { JetpackCompose } from '@nativescript/jetpack-compose';
161+
162+
registerElement('JetpackCompose', () => JetpackCompose)
163+
----
164+
165+
C. Insert into any NativeScript layout
166+
167+
`app/main-page.xml`
168+
169+
[, xml]
170+
----
171+
<Page
172+
xmlns="http://schemas.nativescript.org/tns.xsd"
173+
xmlns:jc="@nativescript/jetpack-compose"
174+
class="page">
175+
<StackLayout>
176+
<jc:JetpackCompose composeId="sampleView" height="100" />
177+
</StackLayout>
178+
</Page>
179+
----
180+
181+
You can now run the app with `ns debug android`.
182+
183+
== Use Android Studio to develop and preview Jetpack Compose
184+
185+
After running the app once you can open the `platforms/android` folder in Android Studio where you'll be able to find the `BasicView.kt` file. From there you can start modifying it and previewing your changes (by adding the `@Preview` decorator on the `@Composable` you want to preview).
186+
187+
IMPORTANT: Saving this file will not change the BasicView.kt that lives inside your App_Resources, so be VERY careful to copy the file contents back once you're done editing it! This will become a DX improvement in the future.
188+
189+
Alternatively, you can create a https://proandroiddev.com/create-an-android-library-aar-79d2338678ba[new Android library, window=_blank] and develop all your Jetpack Compose views there.
190+
191+
== Sending and receiving data to/from NativeScript
192+
193+
First, let's add some bindings to our BasicView so it now receives data in `updateData` and displays that, as well as output an event once the data is updated:
194+
195+
[, js]
196+
----
197+
package com.example
198+
199+
import android.content.Context
200+
import androidx.compose.material.MaterialTheme
201+
import androidx.compose.material.Text
202+
import androidx.compose.runtime.Composable
203+
import androidx.compose.runtime.getValue
204+
import androidx.compose.runtime.mutableStateOf
205+
import androidx.compose.runtime.setValue
206+
import androidx.compose.ui.platform.ComposeView
207+
import androidx.lifecycle.ViewModel
208+
import androidx.lifecycle.viewmodel.compose.viewModel
209+
210+
class BasicView {
211+
data class ExampleUiState(
212+
val text: String = ""
213+
) {}
214+
class ExampleViewModel(
215+
) : ViewModel() {
216+
217+
var uiState by mutableStateOf(ExampleUiState())
218+
}
219+
220+
var mViewModel = ExampleViewModel()
221+
fun generateComposeView(view: ComposeView): ComposeView {
222+
223+
return view.apply {
224+
setContent {
225+
MaterialTheme {
226+
227+
val uiState = mViewModel.uiState;
228+
// In Compose world
229+
Text(uiState.text)
230+
}
231+
}
232+
}
233+
}
234+
235+
fun updateData(value: Map<Any, Any>) {
236+
val v = value["data"] as String;
237+
onEvent?.invoke(v)
238+
mViewModel.uiState = ExampleUiState(v);
239+
}
240+
241+
var onEvent: ((String) -> Unit)? = null
242+
243+
}
244+
----
245+
246+
== Use your Jetpack Compose in a NativeScript layout
247+
248+
`app/main-page.xml:`
249+
250+
[, xml]
251+
----
252+
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page"
253+
xmlns:jc="@nativescript/jetpack-compose">
254+
<StackLayout>
255+
<Label text="The following view is Jetpack Compose inside NativeScript!" textWrap="true"></Label>
256+
<jc:JetpackCompose composeEvent="{{ onEvent }}" data="{{ text }}" composeId="sampleView"></sw:JetpackCompose>
257+
<Label text="This is NativeScript again"></Label>
258+
<TextView textChange="{{ onTextChange }}" text="{{ text }}" textWrap="true"></TextView>
259+
</StackLayout>
260+
</Page>
261+
----
262+
263+
`app/main-page.ts:`
264+
265+
[, js]
266+
----
267+
import { Observable } from '@nativescript/core';
268+
import { registerJetpackCompose, ComposeDataDriver } from '@nativescript/jetpack-compose';
269+
import { EventData, Page, PropertyChangeData } from '@nativescript/core';
270+
271+
// A. You can generate types for your own Compose Provider with 'ns typings android --aar {path/to/{name}.aar}'
272+
// B. Otherwise you can ignore by declaring the package resolution path you know you provided
273+
declare var com;
274+
registerJetpackCompose('sampleView', (view) => new ComposeDataDriver(new com.example.BasicView(), view));
275+
276+
export function navigatingTo(args: EventData) {
277+
const page = <Page>args.object;
278+
page.bindingContext = new DemoModel();
279+
}
280+
281+
export class DemoModel extends Observable {
282+
text = '';
283+
284+
onEvent(evt: JetpackComposeEventData<string>) {
285+
console.log('onEvent', evt.data);
286+
}
287+
288+
onTextChange(evt: PropertyChangeData) {
289+
console.log('textChange', evt.value);
290+
this.set('text', evt.value);
291+
}
292+
}
293+
----
294+
295+
Now every time you change the text on the NativeScript `TextView` it'll update the text on the Jetpack Compose view!
296+
297+
video::s_Q3gQz_Tqo[Use Jetpack Compose with NativeScript via @nativescript/jetpack-compose with data bindings.]
298+
299+
== ColorPicker example
300+
301+
Here's another example where I use a ColorPicker to change a NativeScript view's background color:
302+
303+
`app.gradle`
304+
305+
[, java]
306+
----
307+
implementation "com.github.skydoves:colorpicker-compose:1.0.0"
308+
----
309+
310+
[, java]
311+
----
312+
package com.example
313+
314+
import android.content.Context
315+
import androidx.compose.foundation.layout.fillMaxSize
316+
import androidx.compose.foundation.layout.fillMaxWidth
317+
import androidx.compose.foundation.layout.height
318+
import androidx.compose.foundation.layout.padding
319+
import androidx.compose.material.MaterialTheme
320+
import androidx.compose.material.Text
321+
import androidx.compose.runtime.Composable
322+
import androidx.compose.runtime.getValue
323+
import androidx.compose.runtime.mutableStateOf
324+
import androidx.compose.runtime.setValue
325+
import androidx.compose.ui.Modifier
326+
import androidx.compose.ui.graphics.Color
327+
import androidx.compose.ui.graphics.ImageBitmap
328+
import androidx.compose.ui.platform.ComposeView
329+
import androidx.compose.ui.res.imageResource
330+
import androidx.compose.ui.unit.dp
331+
import androidx.lifecycle.ViewModel
332+
import androidx.lifecycle.viewmodel.compose.viewModel
333+
import com.github.skydoves.colorpicker.compose.ColorEnvelope
334+
import com.github.skydoves.colorpicker.compose.HsvColorPicker
335+
import com.github.skydoves.colorpicker.compose.ImageColorPicker
336+
import com.github.skydoves.colorpicker.compose.rememberColorPickerController
337+
338+
class ColorPickerCompose {
339+
fun generateComposeView(view: ComposeView): ComposeView {
340+
return view.apply {
341+
setContent {
342+
val controller = rememberColorPickerController()
343+
HsvColorPicker(
344+
modifier = Modifier
345+
.fillMaxWidth()
346+
.height(450.dp)
347+
.padding(10.dp),
348+
controller = controller,
349+
onColorChanged = { colorEnvelope: ColorEnvelope ->
350+
onEvent?.invoke(colorEnvelope.hexCode)
351+
}
352+
)
353+
}
354+
}
355+
}
356+
357+
fun updateData(value: Map<Any, Any>) {}
358+
359+
var onEvent: ((String) -> Unit)? = null
360+
361+
}
362+
----
363+
364+
[, xml]
365+
----
366+
<StackLayout backgroundColor="{{ backgroundColor }}">
367+
<Label text="The following view is Jetpack Compose inside NativeScript!" textWrap="true"></Label>
368+
<StackLayout backgroundColor="lightblue">
369+
<jc:JetpackCompose composeEvent="{{ onEvent }}" data="{{ text }}" composeId="jetpackCompose"></sw:JetpackCompose>
370+
</StackLayout>
371+
<Label text="This is NativeScript again"></Label>
372+
<TextView text="{{ backgroundColor }}" textWrap="true"></TextView>
373+
</StackLayout>
374+
----
375+
376+
video::GcZ156BCGr0[Use Jetpack Compose with NativeScript via @nativescript/jetpack-compose]
377+
378+
== Final considerations
379+
380+
Working with Jetpack Compose in NativeScript is very transparent and easy. We look forward in seeing what the community will build with yet another powerful tool in NativeScript's belt!
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"title": "Introduction to Jetpack Compose for NativeScript",
3+
"order": 50,
4+
"domains": ["dev_quality_assurance"],
5+
"authorImg": "assets/articles/introduction-to-jetpack-compose-for-ns/eduardo.jpg",
6+
"language": "en",
7+
"bgImg": "assets/articles/introduction-to-jetpack-compose-for-ns/title_img.png",
8+
"author": "Eduardo Speroni",
9+
"position": "JS Developer",
10+
"date": "Mon Jan 05 2021 10:45:55 GMT+0000 (Coordinated Universal Time)",
11+
"seoDescription": "We collaborated with nStudio to provide an effective and enjoyable Jetpack Compose integration for Android apps driven by NativeScript."
12+
}
112 KB
Loading

0 commit comments

Comments
 (0)