Skip to content

Instantly share code, notes, and snippets.

@luffyxddev
Last active December 19, 2024 19:13
Show Gist options
  • Save luffyxddev/bd2195cc3a91d8b4a97041a7735616e3 to your computer and use it in GitHub Desktop.
Save luffyxddev/bd2195cc3a91d8b4a97041a7735616e3 to your computer and use it in GitHub Desktop.
Android Counter Widget - Example

Android Counter Widget - Example

Important: In this example i used the default tauri plugin template with desktop support (we will add dummy data for desktop, sorry was lazy)

Need to Know

To use this example you need to be familiar with rust, kotlin, android and for frontend bindings javascript .

Getting Started

1. Create an plugin project with npx @tauri-apps/cli plugin new [name] 2. Install javascript depencedies with npm install and install the @tauri-apps/cli if needed for next step 3. Go into your plugin Project and init android with cargo tauri plugin android init and don't follow instructions just add inside your Cargo.toml the tauri-build = "2.0.2" crate and your src/mobile.rs and edit this line

let handle = api.register_android_plugin("[package-id]", "ExamplePlugin")?; // [package-id] from step 1 example: com.plugin.counter

now we have a working plugin. i hope

Setup our Widget

Go to the android/src/main and create a folder called res inside this folder drawable, layout, xml

copy a png icon icon.png into drawable

Widget Layout

Create a File called widget.xml inside the layout folder with following content.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:background="#33000000"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:padding="10dp">
    <ImageView
        android:id="@+id/counterImage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawabl/icon"/>
    <TextView
        android:id="@+id/counterTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_margin="8dp"
        android:text="0"
        android:textColor="#ffffff"
        android:textSize="30sp"
        android:textStyle="bold" />
      <Button
        android:id="@+id/counterButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CLICK"/>  
</LinearLayout>

Widget Info

Create a file called widget_info.xml inside the xml folder with following content.

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialKeyguardLayout="@layout/widget"
    android:initialLayout="@layout/widget"
    android:minHeight="40dp"
    android:minWidth="110dp"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="18000000"
    android:widgetCategory="home_screen"></appwidget-provider>

Edit the android/src/main/AndroidManifest.xml

It should Look like here:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <application android:enableOnBackInvokedCallback="true">
        <receiver android:name=".ExampleAppWidgetProvider" android:exported="true">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="[package-id].BUTTON_CLICK" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>
    </application>
</manifest>

Widget Logic

Go into android/src/main/java and edit the ExamplePlugin.kt

add imports

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import android.content.ComponentName

create a global object

object GlobalVariables {
  var counter = 0
}

create a class named ExampleAppWidgetProvider

@TauriPlugin
class ExampleAppWidgetProvider : AppWidgetProvider() {

    companion object {
        private const val BUTTON_CLICK_ACTION = "[package-id].BUTTON_CLICK"
    }

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)

        if (intent.action == BUTTON_CLICK_ACTION) {
            GlobalVariables.counter++

            val appWidgetManager = AppWidgetManager.getInstance(context)
            val appWidgetIds = appWidgetManager.getAppWidgetIds(
                ComponentName(context, ExampleAppWidgetProvider::class.java)
            )
            for (appWidgetId in appWidgetIds) {
                updateAppWidget(context, appWidgetManager, appWidgetId)
            }
        }
    }

    private fun updateAppWidget(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetId: Int
    ) {
        val views = RemoteViews(context.packageName, R.layout.widget)
        val intent = Intent(context, ExampleAppWidgetProvider::class.java).apply {
            action = BUTTON_CLICK_ACTION
        }
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        views.setOnClickPendingIntent(R.id.counterButton, pendingIntent)
        views.setTextViewText(R.id.counterTextView, "${GlobalVariables.counter}")
        appWidgetManager.updateAppWidget(appWidgetId, views)
    }
}

extend the ExamplePlugin class with this code

just add this under the ping function

@Command
fun getCounter(invoke: Invoke) {
    val ret = JSObject()
    ret.put("value", "${GlobalVariables.counter}")
    invoke.resolve(ret)
}

Let's Jump over to Rust

crate a command inside the commands.rs

#[command]
pub(crate) fn get_counter<R: Runtime>(
    app: AppHandle<R>,
) -> Result<CounterResponse> {
    app.android_widget_counter().get_counter()
}

open the desktop.rs and add a dummy for the lsp

just add it under the ping function

pub fn get_counter(&self) -> crate::Result<CounterResponse> {
    Ok(CounterResponse{
        value: None
    })
}

open the models.rs and add this structs

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CounterRequest {}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CounterResponse {
  pub value: Option<String>,
}

open the mobile.rs and add this code under the ping function

Info: here we are calling the Kotlin function

pub fn get_counter(&self) -> crate::Result<CounterResponse> {
    self
      .0
      .run_mobile_plugin("getCounter", CounterRequest{})
      .map_err(Into::into)
}

add the command get_counter to the build.rs and edit permissions/default.toml add allow-get-counter to default permissions

Now our Javascript binding

go to guest-js/index.ts and add this function

export async function getCounter(): Promise<string | null> {  
  return await invoke('plugin:android_widget_counter|get_counter')
}

How to use it

add the plugin to your Cargo.toml

add the plugin to your tauri project and init the plugin

    tauri::Builder::default()
        .plugin(your_widget_plugin_name::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");

frontend use invoke like that to get the current count

try {
    let val = await invoke<{value:string}>("plugin:your-plugin-namespace|get_counter");
    console.log(val.value);
} catch (e) {
    console.error(e);
        
}

Now it is ready to install

Drag your widget to the homescreen and click a few times ... open your app and check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment