Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2e03469
docs(cli): restructure README installation and fix broken badges
ChiragAgg5k Oct 16, 2026
cf9a153
docs(cli): take the README license badge from the spec like the licen…
ChiragAgg5k Oct 16, 2026
a56bc95
fix: replace broken README badges across SDK templates
ChiragAgg5k Oct 16, 2026
72a9ec2
docs(python,ruby): refresh README install steps and links
ChiragAgg5k Oct 16, 2026
f889c15
docs(go,rust): replace stale README usage with verified getting started
ChiragAgg5k Oct 16, 2026
f4139e1
docs(swift): fix SPM install steps in the Swift and Apple READMEs
ChiragAgg5k Oct 16, 2026
3f6b0e8
docs(dart,flutter): restructure READMEs around install, getting start…
ChiragAgg5k Oct 16, 2026
10ae1c7
docs(android,kotlin): refresh README install and structure
ChiragAgg5k Oct 16, 2026
a4b0d49
docs(js): update web, node and react-native README templates
ChiragAgg5k Oct 16, 2026
a58505d
docs(plugins): correct install steps in skills and plugin READMEs
ChiragAgg5k Oct 16, 2026
65b7256
docs(php,dotnet,unity): fix README install steps and broken links
ChiragAgg5k Oct 16, 2026
b316dd6
Merge branch 'docs/cli-readme-template' into docs/sdk-readmes
ChiragAgg5k Oct 16, 2026
cea7336
Merge branch 'readme/js' into docs/sdk-readmes
ChiragAgg5k Oct 16, 2026
4eb05af
Merge branch 'readme/dart' into docs/sdk-readmes
ChiragAgg5k Oct 16, 2026
1e662d2
Merge branch 'readme/jvm' into docs/sdk-readmes
ChiragAgg5k Oct 16, 2026
9bae9ef
Merge branch 'readme/swift' into docs/sdk-readmes
ChiragAgg5k Oct 16, 2026
654533b
Merge branch 'readme/python-ruby' into docs/sdk-readmes
ChiragAgg5k Oct 16, 2026
8b35249
Merge branch 'readme/php-dotnet' into docs/sdk-readmes
ChiragAgg5k Oct 16, 2026
7d3a9c9
Merge branch 'readme/go-rust' into docs/sdk-readmes
ChiragAgg5k Oct 16, 2026
24f049d
Merge branch 'readme/plugins' into docs/sdk-readmes
ChiragAgg5k Oct 16, 2026
e3ba16d
fix: point plugin homepages and license URL at current locations
ChiragAgg5k Oct 16, 2026
2dc98fc
fix(android): split the push notification paragraph under the twig li…
ChiragAgg5k Oct 17, 2026
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
2 changes: 1 addition & 1 deletion example.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ final class Config
public const string TITLE = 'Appwrite';
public const string DESCRIPTION = 'Appwrite backend as a service';
public const string LICENSE_NAME = 'BSD-3-Clause';
public const string LICENSE_URL = 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE';
public const string LICENSE_URL = 'https://raw.githubusercontent.com/appwrite/appwrite/main/LICENSE';
public const string COVER_IMAGE = 'https://github.com/appwrite/appwrite/raw/main/public/images/github.png';
public const string TWITTER = 'appwrite';
public const string DISCORD_CHANNEL = '564160730845151244';
Expand Down
164 changes: 60 additions & 104 deletions templates/android/README.md.twig
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# {{ spec.info.title }} {{sdk.name}} SDK

![Maven Central](https://img.shields.io/maven-central/v/{{ sdk.namespace | caseDot }}/{{ sdk.gitRepoName | caseDash }}.svg?color=green&style=flat-square)
![License](https://img.shields.io/github/license/{{ sdk.gitUserName|url_encode }}/{{ sdk.gitRepoName|url_encode }}.svg?style=flat-square)
![License](https://img.shields.io/badge/license-{{ spec.info.license.name|replace({'-': '--', '_': '__'})|url_encode }}-blue.svg?style=flat-square)
![Version](https://img.shields.io/badge/api%20version-{{ spec.info.version|url_encode }}-blue.svg?style=flat-square)
[![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator)
{% if sdk.twitterHandle %}
[![Twitter Account](https://img.shields.io/twitter/follow/{{ sdk.twitterHandle }}?color=00acee&label=twitter&style=flat-square)](https://twitter.com/{{ sdk.twitterHandle }})
[![X Account](https://img.shields.io/badge/follow-%40{{ sdk.twitterHandle|replace({'-': '--', '_': '__'})|url_encode }}-000000?logo=x&style=flat-square)](https://x.com/{{ sdk.twitterHandle }})
{% endif %}
{% if sdk.discordChannel %}
[![Discord](https://img.shields.io/discord/{{ sdk.discordChannel }}?label=discord&style=flat-square)]({{ sdk.discordUrl }})
Expand All @@ -23,134 +22,90 @@

## Installation

### Gradle
The SDK requires Android 7.0 (API level 24) or later and is published to Maven Central as `{{ sdk.namespace | caseDot }}:{{ sdk.gitRepoName | caseDash }}`. Make sure `mavenCentral()` is among your repositories (new Android Studio projects include it in `settings.gradle.kts`), then add the dependency to your app module's build file.

Appwrite's Android SDK is hosted on Maven Central. In order to fetch the Appwrite SDK, add this to your root level `build.gradle(.kts)` file:
Kotlin DSL (`build.gradle.kts`):

```groovy
repositories {
mavenCentral()
```kotlin
dependencies {
implementation("{{ sdk.namespace | caseDot }}:{{ sdk.gitRepoName | caseDash }}:{{ sdk.version }}")
}
```

If you would like to fetch our SNAPSHOT releases, you need to add the SNAPSHOT maven repository to your `build.gradle(.kts)`:
Groovy (`build.gradle`):

```groovy
repositories {
maven {
url "https://s01.oss.sonatype.org/content/repositories/snapshots/"
}
dependencies {
implementation '{{ sdk.namespace | caseDot }}:{{ sdk.gitRepoName | caseDash }}:{{ sdk.version }}'
}
```

Next, add the dependency to your project's `build.gradle(.kts)` file:

```groovy
implementation("{{ sdk.namespace | caseDot }}:{{ sdk.gitRepoName | caseDash }}:{{ sdk.version }}")
```
### Code shrinking (R8)

### Maven
Add this to your project's `pom.xml` file:
The SDK ships the R8 rules it needs, so no extra configuration is required. Your own classes that the SDK fills from JSON (the `nestedType` of documents, rows and preferences, and the `payloadType` of Realtime subscriptions) are read by Gson through reflection. Annotate them with `@Keep` so R8 keeps their fields:

```xml
<dependencies>
<dependency>
<groupId>{{ sdk.namespace | caseDot }}</groupId>
<artifactId>{{ sdk.gitRepoName | caseDash }}</artifactId>
<version>{{sdk.version}}</version>
</dependency>
</dependencies>
```kotlin
@Keep
data class Note(val title: String, val body: String)
```
{% if sdk.gettingStarted %}

### Code shrinking (R8)
{{ sdk.gettingStarted|trim|raw }}
{% endif %}

## Push notifications

The SDK ships the R8 rules it needs, so no extra configuration is required. Your own classes
that the SDK fills from JSON (the `nestedType` of documents, rows and preferences, and the
`payloadType` of Realtime subscriptions) are read by Gson through reflection. Annotate them
with `@Keep` so R8 keeps their fields:
`Push` subscribes to topics over the Appwrite push broker. With no topic it subscribes to the signed-in user's own topic:

```kotlin
@Keep
data class Note(val title: String, val body: String)
import {{ sdk.namespace | caseDot }}.services.Push

val push = Push(client, context)

val subscription = push.subscribe { message ->
Log.d("Appwrite", "${message.topic}: ${message.data}")
}

subscription.unsubscribe()
```

### Push notifications

#### Background delivery

A subscription with `background = true` keeps delivering after the app is backgrounded, its
process is killed, or the device restarts, until it is unsubscribed or `push.close()` is
called (do this on sign-out). No service is kept running: a scheduled job and alarm wake the
app every 15 to 60 seconds to reconnect, and the broker replays what was sent in between
(`retry = true`). It reconnects with the credential saved at subscribe time, so use a session
(`client.setSession(...)`), not a short-lived JWT.

While the app is not on screen, each message is also posted as a notification that opens
your launch activity with `Push.EXTRA_TOPIC` and `Push.EXTRA_PAYLOAD` in its extras. It shows the
title, body and image sent with `createPush`, falling back to the subscription's `title`, and to
the raw payload as the body for messages that are not a JSON object. A push with neither a title
nor a body, such as a data-only push, posts no notification; it still reaches your callback or
`PushReceiver`. On Android 13 and later it needs the `POST_NOTIFICATIONS`
permission: the first background subscription asks for it from the visible activity (the one
`Push` is created with, else the next one to resume), or call
`Push.requestNotificationPermission(activity)` to ask yourself. Set its
icon with `<meta-data android:name="{{ sdk.namespace | caseDot }}.push.notification_icon" android:resource="@drawable/..." />`
in your `<application>`. While the app is on screen your callback shows the message, so no
notification is posted unless the subscription passes `notifyInForeground = true`. To handle
messages that arrive while no callback is listening in code, subclass `PushReceiver` and declare
it with the `{{ sdk.namespace | caseDot }}.push.MESSAGE` action (see its docs).

#### Delivery while the app is closed

Messages sent while the app is closed arrive at the next scheduled wake-up. While the device is
awake that is about every 15 seconds, or about every 60 seconds once exact alarms are allowed;
without exact alarms the wake-ups are inexact, so battery saver can defer them further. In Doze
(screen off and idle for a while) Android limits background alarms, exact ones included, to about
one every nine minutes, so a closed app can take several minutes to receive a message: allowing
exact alarms makes wake-ups punctual, it does not lift Doze. For immediate delivery, also in
Doze, use `push.setForeground(true)` (see below).
### Background delivery

A subscription with `background = true` keeps delivering after the app is backgrounded, its process is killed, or the device restarts, until it is unsubscribed or `push.close()` is called (do this on sign-out). No service is kept running: a scheduled job and alarm wake the app every 15 to 60 seconds to reconnect, and the broker replays what was sent in between (`retry = true`). It reconnects with the credential saved at subscribe time, so use a session (`client.setSession(...)`), not a short-lived JWT.

While the app is not on screen, each message is also posted as a notification that opens your launch activity with `Push.EXTRA_TOPIC` and `Push.EXTRA_PAYLOAD` in its extras. It shows the title, body and image sent with `createPush`, falling back to the subscription's `title`, and to the raw payload as the body for messages that are not a JSON object. A push with neither a title nor a body, such as a data-only push, posts no notification; it still reaches your callback or `PushReceiver`.

On Android 13 and later it needs the `POST_NOTIFICATIONS` permission: the first background subscription asks for it from the visible activity (the one `Push` is created with, else the next one to resume), or call `Push.requestNotificationPermission(activity)` to ask yourself. Set its icon with `<meta-data android:name="{{ sdk.namespace | caseDot }}.push.notification_icon" android:resource="@drawable/..." />` in your `<application>`.

While the app is on screen your callback shows the message, so no notification is posted unless the subscription passes `notifyInForeground = true`. To handle messages that arrive while no callback is listening in code, subclass `PushReceiver` and declare it with the `{{ sdk.namespace | caseDot }}.push.MESSAGE` action (see its docs).

### Delivery while the app is closed

Messages sent while the app is closed arrive at the next scheduled wake-up. While the device is awake that is about every 15 seconds, or about every 60 seconds once exact alarms are allowed; without exact alarms the wake-ups are inexact, so battery saver can defer them further. In Doze (screen off and idle for a while) Android limits background alarms, exact ones included, to about one every nine minutes, so a closed app can take several minutes to receive a message: allowing exact alarms makes wake-ups punctual, it does not lift Doze. For immediate delivery, also in Doze, use `push.setForeground(true)` (see below).

The SDK uses exact alarms on its own whenever the app may schedule them. To allow it:

1. Declare the permissions in your app's `AndroidManifest.xml`. Both are optional and subject to
Google Play policy: `SCHEDULE_EXACT_ALARM` needs a declaration in the Play Console, and
`USE_EXACT_ALARM` is reserved for alarm, clock and calendar apps (the SDK does not use it).
1. Declare the permissions in your app's `AndroidManifest.xml`. Both are optional and subject to Google Play policy: `SCHEDULE_EXACT_ALARM` needs a declaration in the Play Console, and `USE_EXACT_ALARM` is reserved for alarm, clock and calendar apps (the SDK does not use it).

```xml
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
```

2. On Android 13 and later the user has to allow exact alarms, under Settings > Apps > Special app
access > Alarms & reminders. Android 12 grants a declared `SCHEDULE_EXACT_ALARM`
automatically, and older versions need nothing. Check with `Push.backgroundStatus(context)`: when `bestEffort` is
true, explain why to the user, then from a user action open that screen with `Push.requestExactAlarms(context)`, or
ask for the battery-optimisation exemption with `Push.requestIgnoreBatteryOptimizations(context)`. Both return false when there is
nothing to ask, including when the permission is not declared. The SDK never opens these
screens on its own.

If `backgroundRestricted` is true, the user restricted the app's background usage and messages can
wait until the app is opened. Exact alarms don't lift it, and neither does foreground mode: Android
stops a restricted app's foreground service once it leaves the screen. Ask the user to choose
Unrestricted in the app's battery settings (Settings > Apps > your app > Battery); granting the
battery optimisation exemption also lets the SDK's alarms and jobs run, and `bestEffort` then
reflects only the exact-alarm state.

If the user force-stops the app (Settings > Force stop, and on some devices swiping it away from
recents), Android cancels its alarms and jobs: nothing is delivered until the app is opened
again, and the broker then replays what was sent meanwhile.

For immediate delivery even after a kill and during Doze, call `push.setForeground(true)` from
the foreground: it adds a foreground service
with a quiet ongoing notification. If you never enable it, you can remove the service from
your merged manifest, so the Play Console does not ask you to declare it:
2. On Android 13 and later the user has to allow exact alarms, under Settings > Apps > Special app access > Alarms & reminders. Android 12 grants a declared `SCHEDULE_EXACT_ALARM` automatically, and older versions need nothing. Check with `Push.backgroundStatus(context)`: when `bestEffort` is true, explain why to the user, then from a user action open that screen with `Push.requestExactAlarms(context)`, or ask for the battery-optimisation exemption with `Push.requestIgnoreBatteryOptimizations(context)`. Both return false when there is nothing to ask, including when the permission is not declared. The SDK never opens these screens on its own.

If `backgroundRestricted` is true, the user restricted the app's background usage and messages can wait until the app is opened. Exact alarms don't lift it, and neither does foreground mode: Android stops a restricted app's foreground service once it leaves the screen. Ask the user to choose Unrestricted in the app's battery settings (Settings > Apps > your app > Battery); granting the battery optimisation exemption also lets the SDK's alarms and jobs run, and `bestEffort` then reflects only the exact-alarm state.

If the user force-stops the app (Settings > Force stop, and on some devices swiping it away from recents), Android cancels its alarms and jobs: nothing is delivered until the app is opened again, and the broker then replays what was sent meanwhile.

For immediate delivery even after a kill and during Doze, call `push.setForeground(true)` from the foreground: it adds a foreground service with a quiet ongoing notification. If you never enable it, you can remove the service from your merged manifest, so the Play Console does not ask you to declare it:

```xml
<service android:name="{{ sdk.namespace | caseDot }}.services.PushService" tools:node="remove" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" tools:node="remove" />
```

#### Opening a tapped notification
### Opening a tapped notification

Read the `data` sent with `createPush` when the user taps a background notification:

Expand All @@ -162,18 +117,19 @@ push.getInitialNotification()?.let { openSale(it.data["saleId"]) }
val stop = push.onNotificationOpened { opened -> openSale(opened.data["saleId"]) }
```

A tap made while nothing listens is kept for `getInitialNotification()`. The tap intent still
carries `Push.EXTRA_TOPIC` and `Push.EXTRA_PAYLOAD` for apps that read their activity's intent.
A tap made while nothing listens is kept for `getInitialNotification()`. The tap intent still carries `Push.EXTRA_TOPIC` and `Push.EXTRA_PAYLOAD` for apps that read their activity's intent.

{% if sdk.gettingStarted %}
## Learn more

{{ sdk.gettingStarted|raw }}
{% endif %}
- [Android quick start](https://appwrite.io/docs/quick-starts/android) and [Java quick start](https://appwrite.io/docs/quick-starts/android-java)
- [Android SDK reference](https://appwrite.io/docs/references/cloud/client-android-kotlin/account)
- [Appwrite docs](https://appwrite.io/docs)
- [Discord community](https://appwrite.io/discord)

## Contribution

This library is auto-generated by Appwrite custom [SDK Generator](https://github.com/appwrite/sdk-generator). To learn more about how you can help us improve this SDK, please check the [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md) before sending a pull-request.
This library is auto-generated by the Appwrite [SDK Generator](https://github.com/appwrite/sdk-generator). To learn how you can help improve this SDK, please read the [contribution guide](https://github.com/appwrite/sdk-generator/blob/main/CONTRIBUTING.md) before sending a pull request.

## License

Please see the [{{spec.info.license.name}} license]({{spec.info.license.url}}) file for more information.
Please see the [{{spec.info.license.name}} license]({{spec.info.license.url}}) file for more information.
Loading
Loading