Important: In this example i used the default tauri plugin template with desktop support (we will add dummy data for desktop, sorry was lazy)
To use this example you need to be familiar with rust
, kotlin
, android
and for frontend bindings javascript
.
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
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
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>
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>
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>
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)
}
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
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')
}
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);
}
Drag your widget to the homescreen and click a few times ... open your app and check.