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

Move lifecycle to activity plugin #46

Merged
merged 2 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 40 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ The bootstrapper for WebView-based Android apps.
- [Google Sign-In](#google-sign-in-plugin)
- [HTTPS](#https-plugin)
- [Keyboard](#keyboard-plugin)
- [Lifecycle](#lifecycle-plugin)
- [Network](#network-plugin)
- [Notifications](#notifications-plugin)
- [Permissions](#permissions-plugin)
Expand Down Expand Up @@ -75,9 +74,7 @@ instance that would be responsible for event marshalling:
```kotlin
import org.racehorse.EventBridge

val eventBridge = EventBridge(webView)

eventBridge.enable()
val eventBridge = EventBridge(webView).also { enable() }
```

Racehorse uses a [Greenrobot EventBus](https://greenrobot.org/eventbus) to deliver events to subscribers, so bridge must
Expand Down Expand Up @@ -351,15 +348,23 @@ class ShowToastEvent(val message: String) : WebEvent
[`ActivityManager`](https://smikhalevski.github.io/racehorse/interfaces/racehorse.ActivityManager.html) starts
activities and provides info about the activity that renders the WebView.

1. Initialize the plugin in your Android app:
1. Add Lifecycle dependency to your Android app:

```kotlin
dependencies {
implementation("androidx.lifecycle:lifecycle-process:2.6.2")
}
```

2. Initialize the plugin in your Android app:

```kotlin
import org.racehorse.ActivityPlugin

EventBus.getDefault().register(ActivityPlugin(activity))
EventBus.getDefault().register(ActivityPlugin().also { enable() })
```

2. Start the activity, for example to open settings app and navigate user to the notification settings:
3. Start a new activity. For example, here's how to open Settings app and navigate user to the notification settings:

```ts
import { activityManager, Intent } from 'racehorse';
Expand All @@ -373,6 +378,34 @@ activityManager.startActivity({
});
```

4. Synchronously read the status of the current activity or subscribe to its changes:

```ts
import { activityManager, ActivityState } from 'racehorse';

activityManager.getActivityState();
// ⮕ ActivityState.BACKGROUND

activityManager.subscribe(state => {
// React to activity state changes
});

activityManager.subscribe('foreground', () => {
// React to activity entering foreground
});
```

If you are using React, then refer to
[`useActivityState`](https://smikhalevski.github.io/racehorse/functions/_racehorse_react.useActivityState.html) hook
that re-renders a component when activity state changes.

```tsx
import { useActivityState } from '@racehorse/react';

const state = useActivityState();
// ⮕ ActivityState.BACKGROUND
```

# Asset loader plugin

Asset loader plugin requires [WebView events](#webview-events) to be enabled.
Expand Down Expand Up @@ -1010,48 +1043,6 @@ status.height;
// ⮕ 630
```

# Lifecycle plugin

[`LifecycleManager`](https://smikhalevski.github.io/racehorse/interfaces/racehorse.LifecycleManager.html) enables
lifecycle state monitoring.

1. Add Lifecycle dependency to your Android app:

```kotlin
dependencies {
implementation("androidx.lifecycle:lifecycle-process:2.6.2")
}
```

2. Initialize the plugin in your Android app:

```kotlin
import org.racehorse.LifecyclePlugin

val lifecyclePlugin = LifecyclePlugin()

EventBus.getDefault().register(lifecyclePlugin)

lifecyclePlugin.enable()
```

3. Synchronously read the lifecycle state or subscribe to its changes:

```ts
import { lifecycleManager, LifecycleState } from 'racehorse';

lifecycleManager.getLifecycleState();
// ⮕ LifecycleState.STARTED

lifecycleManager.subscribe(state => {
// React to lifecycle state changes
});

lifecycleManager.subscribe('pause', () => {
// React to app being paused
});
```

# Network plugin

[`NetworkManager`](https://smikhalevski.github.io/racehorse/interfaces/racehorse.NetworkManager.html) enables network
Expand Down
2 changes: 1 addition & 1 deletion android/example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ dependencies {
implementation("org.greenrobot:eventbus:3.3.1")
implementation("com.google.code.gson:gson:2.10.1")

// LifecyclePlugin
// ActivityPlugin
implementation("androidx.lifecycle:lifecycle-process:2.6.2")

// AssetLoaderPlugin
Expand Down
3 changes: 1 addition & 2 deletions android/example/src/main/java/com/example/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class MainActivity : AppCompatActivity() {
eventBus.register(HttpsPlugin())
eventBus.register(networkPlugin)
eventBus.register(KeyboardPlugin(this))
eventBus.register(ActivityPlugin(this))
eventBus.register(ActivityPlugin(this).apply { enable() })
eventBus.register(DeepLinkPlugin())
eventBus.register(PermissionsPlugin(this))
eventBus.register(NotificationsPlugin(this))
Expand All @@ -66,7 +66,6 @@ class MainActivity : AppCompatActivity() {
eventBus.register(FacebookSharePlugin(this))
eventBus.register(BiometricPlugin(this))
eventBus.register(BiometricEncryptedStoragePlugin(this, File(filesDir, "biometric_storage")))
eventBus.register(LifecyclePlugin().apply { enable() })
eventBus.register(ToastPlugin(this))

@Suppress("DEPRECATION")
Expand Down
2 changes: 1 addition & 1 deletion android/racehorse/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ dependencies {
compileOnly("org.greenrobot:eventbus:3.3.1")
compileOnly("com.google.code.gson:gson:2.10.1")

// LifecyclePlugin
// ActivityPlugin
compileOnly("androidx.lifecycle:lifecycle-process:2.6.2")

// AssetLoaderPlugin
Expand Down
49 changes: 47 additions & 2 deletions android/racehorse/src/main/java/org/racehorse/ActivityPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package org.racehorse

import android.app.Activity
import androidx.activity.ComponentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.racehorse.utils.SerializableIntent
import org.racehorse.utils.launchActivity
Expand All @@ -10,6 +13,12 @@ import java.io.Serializable

class ActivityInfo(val packageName: String) : Serializable

class ActivityStateChangedEvent(val activityState: Int) : NoticeEvent

class GetActivityStateEvent : RequestEvent() {
class ResultEvent(val activityState: Int) : ResponseEvent()
}

class GetActivityInfoEvent : RequestEvent() {
class ResultEvent(val activityInfo: ActivityInfo) : ResponseEvent()
}
Expand All @@ -31,11 +40,47 @@ class StartActivityForResultEvent(val intent: SerializableIntent) : RequestEvent
}

/**
* Opens URL in an external app.
* Launches activities for various intents, and provides info about the current activity.
*
* @param activity The activity that launches the intent to open a URL.
* @param eventBus The event bus to which events are posted.
*/
open class ActivityPlugin(private val activity: ComponentActivity) {
open class ActivityPlugin(
private val activity: ComponentActivity,
private val eventBus: EventBus = EventBus.getDefault()
) {

companion object {
private const val BACKGROUND = 0
private const val FOREGROUND = 1
private const val ACTIVE = 2
}

private val lifecycleListener = LifecycleEventObserver { _, event ->
when (event.targetState) {
Lifecycle.State.CREATED -> eventBus.post(ActivityStateChangedEvent(BACKGROUND))
Lifecycle.State.STARTED -> eventBus.post(ActivityStateChangedEvent(FOREGROUND))
Lifecycle.State.RESUMED -> eventBus.post(ActivityStateChangedEvent(ACTIVE))
else -> {}
}
}

open fun enable() = activity.lifecycle.addObserver(lifecycleListener)

open fun disable() = activity.lifecycle.removeObserver(lifecycleListener)

@Subscribe
open fun onGetActivityState(event: GetActivityStateEvent) {
event.respond(
GetActivityStateEvent.ResultEvent(
when (activity.lifecycle.currentState) {
Lifecycle.State.INITIALIZED, Lifecycle.State.CREATED, Lifecycle.State.DESTROYED -> BACKGROUND
Lifecycle.State.STARTED -> FOREGROUND
Lifecycle.State.RESUMED -> ACTIVE
}
)
)
}

@Subscribe
open fun onGetActivityInfo(event: GetActivityInfoEvent) {
Expand Down
50 changes: 0 additions & 50 deletions android/racehorse/src/main/java/org/racehorse/LifecyclePlugin.kt

This file was deleted.

2 changes: 0 additions & 2 deletions web/example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ import { FacebookShareExample } from './examples/FacebookShareExample';
import { DownloadExample } from './examples/DownloadExample';
import { BiometricExample } from './examples/BiometricExample';
import { BiometricEncryptedStorageExample } from './examples/BiometricEncryptedStorageExample';
import { LifecycleExample } from './examples/LifecycleExample';

export function App() {
return (
<>
<ToastExample />
<LifecycleExample />
<BiometricExample />
<BiometricEncryptedStorageExample />
<DownloadExample />
Expand Down
32 changes: 28 additions & 4 deletions web/example/src/examples/ActivityExample.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,41 @@
import React, { useState } from 'react';
import { activityManager, ActivityResult, Intent, permissionsManager } from 'racehorse';
import React, { useEffect, useState } from 'react';
import { activityManager, ActivityResult, ActivityState, Intent, permissionsManager } from 'racehorse';
import { FormattedJSON } from '../components/FormattedJSON';
import { useActivityState } from '@racehorse/react';

const activityStateLabel = {
[ActivityState.BACKGROUND]: '🔴 Background',
[ActivityState.FOREGROUND]: '🟡 Foreground',
[ActivityState.ACTIVE]: '🟢 Active',
} as const;

export function ActivityExample() {
const activityState = useActivityState();
const [activityInfo] = useState(activityManager.getActivityInfo);
const [contactActivityResult, setContactActivityResult] = useState<ActivityResult | null>();

useEffect(
() =>
activityManager.subscribe(activityState => {
console.log(activityStateLabel[activityState]);
}),
[]
);

return (
<>
<h2>{'Activity'}</h2>

{'Activity info: '}
<FormattedJSON value={activityInfo} />
<p>{'Activity state: ' + activityStateLabel[activityState]}</p>

<p>
<i>{'Open WebView console to observe activity state changes'}</i>
</p>

<p>
{'Activity info: '}
<FormattedJSON value={activityInfo} />
</p>

<p>
<button
Expand Down
27 changes: 0 additions & 27 deletions web/example/src/examples/LifecycleExample.tsx

This file was deleted.

Loading