Hotwire Native for Android

repository·main·Indexed 41 days ago

https://github.com/hotwired/hotwire-native-android

A Kotlin library that wraps a Hotwire web application in a native Android shell. It provides native navigation UI and high-performance web integration via a managed WebView, allowing developers to leverage existing Hotwire web apps to build mobile applications.

Tokens
5K
Snippets
17
Records
31
Agent score
50%

What's inside Hotwire Native

  1. Overview of Hotwire Native for Android

    main
    Hotwire Native for Android is a high-level native Kotlin library that allows you to leverage your existing Hotwire web app to build mobile applications. It works by wrapping your web app in a native Android shell and managing a single WebView instance across multiple destinations. This approach provides native navigation UI while maintaining the client-side performance benefits of Hotwire.
  2. Run the Demo Android App

    main

    To run the demonstration application, follow these steps:

    1. Clone the repository to your local machine.
    2. Open the project directory in Android Studio.
    3. Select and build the demo module to your connected Android device or emulator.
    # Example workflow
    git clone https://github.com/hotwired/hotwire-native-android.git
    cd hotwire-native-android
    # Open in Android Studio and run the 'demo' module
  3. Configure Hotwire settings with HotwireConfig

    main

    The HotwireConfig class is used to customize the behavior of the Hotwire Native Android library. You can configure logging, WebView debugging, custom User Agents, and bridge component serialization.

    Note: The constructor for HotwireConfig is internal, implying it is typically instantiated or accessed through a central entry point provided by the library (such as a singleton or a builder pattern defined elsewhere in the SDK).

  4. Initialize HotwireBottomNavigationController

    main

    Use HotwireBottomNavigationController to manage a BottomNavigationView where each tab is associated with its own NavigatorHost.

    When instantiating the controller, you can configure several behaviors:

    • initialVisibility: Sets the starting visibility mode (see Visibility).
    • clearNavigationOnTabReselection: If true (default), reselecting the active tab clears its navigation backstack to the start destination. If false, reselecting has no effect.
    • animateVisibilityChanges: If true (default), the bottom navigation bar animates in and out when visibility changes. If false, changes are instant.
    • lazyLoadTabs: If true, a tab's start location is only loaded when it is first selected. If false (default), all tabs load their start locations immediately upon creation.
    val controller = HotwireBottomNavigationController(
        activity = myHotwireActivity,
        view = myBottomNavigationView,
        initialVisibility = HotwireBottomNavigationController.Visibility.DEFAULT,
        clearNavigationOnTabReselection = true,
        animateVisibilityChanges = true,
        lazyLoadTabs = true
    )
  5. Configure AndroidManifest permissions for WebView media capture

    main

    To enable media capture (audio or video) within the WebView, you must declare specific permissions in your host app's AndroidManifest.xml. If these are missing, WebViewPermissionDelegate will deny the request and the web page will receive a NotAllowedError.

    Required Permissions

    WebView ResourceRequired Android Permissions
    RESOURCE_AUDIO_CAPTUREandroid.permission.RECORD_AUDIO AND android.permission.MODIFY_AUDIO_SETTINGS
    RESOURCE_VIDEO_CAPTUREandroid.permission.CAMERA

    Note on Audio: Even though MODIFY_AUDIO_SETTINGS is a normal (install-time) permission, it is strictly required by the Chromium WebView's audio pipeline. Without it, getUserMedia({ audio: true }) will fail with Unable to select communication device! even if RECORD_AUDIO is granted.

  6. Configure PathConfiguration locations

    main

    To load URL path properties, you must provide a Location object to the load method. You can specify where the configuration JSON file is located using two primary methods:

    1. Bundled Asset (Recommended): Provide the path to a file in your app's assets directory. Do not include the assets/ prefix. For example, if your file is at assets/json/configuration.json, use "json/configuration.json".
    2. Remote URL: Provide a full URL to a publicly accessible JSON file on your server. This file will be automatically downloaded and cached at app startup.

    Providing a local configuration file is highly recommended to ensure configuration is available immediately at startup.

    val location = PathConfiguration.Location(
        assetFilePath = "json/configuration.json",
        remoteFileUrl = "https://your-server.com/configuration.json"
    )
  7. Access current destination and location information

    main

    You can inspect the current state of the navigator using these properties:

    • currentDestination: Returns the currently active HotwireDestination on the backstack. If a dialog is active, it returns that dialog destination.
    • location: Returns the URL/location string of the current backstack entry.
    • previousLocation: Returns the URL/location string of the previous backstack entry.
    • isAtStartDestination(): Returns true if the current destination is the only entry in the backstack.
  8. Navigate to a new location with route()

    main

    Use the route() method to perform navigation. The resulting destination and its presentation (e.g., push, replace, modal) are determined by your pathConfiguration rules. You can provide optional VisitOptions, a Bundle for arguments, or FragmentNavigator.Extras for fragment-specific behavior.

    If a custom route decision (via customRouteDecision) returns Router.Decision.CANCEL, the navigation will be aborted.

    navigator.route(
        location = "/new-path",
        options = VisitOptions(action = VisitAction.PUSH),
        bundle = Bundle().apply { putString("key", "value") }
    )
  9. Retrieve path properties for a URL

    main

    Once loaded, you can retrieve a map of properties for a specific URL using the properties(location: String) method. Pass the absolute URL you want to match; the system will use the URL's relative path to find matching regex rules defined in your configuration.

    Returns a PathConfigurationProperties (a HashMap<String, Any>) containing the values defined in your JSON for that path.

    val props = pathConfiguration.properties("https://example.com/products/123")