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.counternow 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.ComponentNamecreate 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
pingfunction
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.