Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
36 changes: 17 additions & 19 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
# Change Log

## 29.1.0-rc.3
## 30.0.0

* Added: first background subscription auto-requests `POST_NOTIFICATIONS` (Android 13+) from the visible activity
* Fixed: `onOpen`/`onClose` callbacks fire correctly when joining or leaving the background host
* Fixed: notification taps are recorded after launching the app, so tap-opened screens land on top
* Breaking: requests send `x-appwrite-response-format: 2.5.0`; responses follow the 2.5 shapes
* Breaking: missing response fields map to their documented defaults
* Breaking: `oauth2` `resource` params now take `List<String>` on the authorize, PAR, device and token methods
* Breaking: removed the `Push` service, `PushReceiver`, `Topic`, `Client.setPushEndpoint` and `setPushClientId`
* Updated: `minSdk` lowered to 23, and the push permissions and services left the manifest
* Fixed: a file exactly `CHUNK_SIZE` bytes long uploads in a single request

## 29.1.0-rc.2
## 29.2.0

* Added: `getInitialNotification()`, `onNotificationOpened()`, and the `PushNotificationOpened` data class (topic + data)
* Added: `onOpen`/`onClose` callbacks now fire for background push connections
* Added: push auth falls back to the client's cookie-store session
* Fixed: a foreground message with no live callback now still posts a notification
* Updated: notification taps route through an internal `PushOpenActivity`
* Added: `analytics.createEvent` in a new `Analytics` service to send tracking events
* Added: optional `state` on `account.createOAuth2Session` and `account.createOAuth2Token`, returned unchanged on the success and failure URLs
* Added: optional `membershipId` on `teams.createMembership`
* Fixed: `avatars.updatePhoto` sends `PUT`, so uploading a profile photo no longer returns 404
* Fixed: OAuth2 query parameters (`success`, `failure`, `scopes`, `state`) are URL-encoded, so values containing `&`, `+` or `#` survive the redirect

## 29.1.0-rc.1
## 29.1.0

* Updated: regenerated with sdk-generator 5.5.0; no SDK code changes

## 29.1.0-rc.0

* Added: optional `method` param on `Client.chunkedUpload()` to set the upload HTTP method
* Added: background push notifications render the server `notification` title, body, and image
* Updated: `Avatars.updatePhoto()` uploads via `PUT`
* Fixed: an exactly-chunk-sized file uploads in a single request
* Added: `account` passkey methods `listPasskeys`, `createPasskey`, `getPasskey`, `updatePasskey`, `deletePasskey` and `updatePasskeyVerification`
* Added: `account.createPasskeyToken` and `account.updatePasskeyToken` for passkey sign-in
* Added: `Passkey`, `PasskeyList`, `PasskeyChallenge` and `TokenSecret` models

## 29.0.0

Expand Down
90 changes: 4 additions & 86 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

![Maven Central](https://img.shields.io/maven-central/v/io.appwrite/sdk-for-android.svg?color=green&style=flat-square)
![License](https://img.shields.io/github/license/appwrite/sdk-for-android.svg?style=flat-square)
![Version](https://img.shields.io/badge/api%20version-2.3.0-blue.svg?style=flat-square)
![Version](https://img.shields.io/badge/api%20version-2.5.0-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)
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord)

**This SDK targets Appwrite server version 2.3.x as shipped on Appwrite Cloud.** Self-hosted releases can lag behind Cloud — if you run an older self-hosted build, use a matching older SDK from [previous releases](https://github.com/appwrite/sdk-for-android/releases) when APIs differ.
**This SDK targets Appwrite server version 2.5.x as shipped on Appwrite Cloud.** Self-hosted releases can lag behind Cloud — if you run an older self-hosted build, use a matching older SDK from [previous releases](https://github.com/appwrite/sdk-for-android/releases) when APIs differ.

Appwrite is an open-source backend as a service server that abstracts and simplifies complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Android SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)

Expand Down Expand Up @@ -38,7 +38,7 @@ repositories {
Next, add the dependency to your project's `build.gradle(.kts)` file:

```groovy
implementation("io.appwrite:sdk-for-android:29.1.0-rc.3")
implementation("io.appwrite:sdk-for-android:30.0.0")
```

### Maven
Expand All @@ -49,7 +49,7 @@ Add this to your project's `pom.xml` file:
<dependency>
<groupId>io.appwrite</groupId>
<artifactId>sdk-for-android</artifactId>
<version>29.1.0-rc.3</version>
<version>30.0.0</version>
</dependency>
</dependencies>
```
Expand All @@ -66,88 +66,6 @@ with `@Keep` so R8 keeps their fields:
data class Note(val title: String, val body: String)
```

### 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 the
raw payload for other messages. 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="io.appwrite.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 `io.appwrite.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).

```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 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="io.appwrite.services.PushService" tools:node="remove" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" tools:node="remove" />
```

#### Opening a tapped notification

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

```kotlin
// The tap that launched the app (reported once, so call it at startup).
push.getInitialNotification()?.let { openSale(it.data["saleId"]) }

// Taps while the app is running, including in the background.
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.


## Getting Started

Expand Down
1 change: 1 addition & 0 deletions docs/examples/java/account/create-o-auth-2-session.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ account.createOAuth2Session(
"https://example.com", // success (optional)
"https://example.com", // failure (optional)
List.of(), // scopes (optional)
"<STATE>", // state (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
Expand Down
1 change: 1 addition & 0 deletions docs/examples/java/account/create-o-auth-2-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ account.createOAuth2Token(
"https://example.com", // success (optional)
"https://example.com", // failure (optional)
List.of(), // scopes (optional)
"<STATE>", // state (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
Expand Down
22 changes: 22 additions & 0 deletions docs/examples/java/account/create-passkey-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
```java
import android.util.Log;

import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;

Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID

Account account = new Account(client);

account.createPasskeyToken(new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}

Log.d("Appwrite", result.toString());
}));
```
27 changes: 27 additions & 0 deletions docs/examples/java/account/create-passkey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
```java
import android.util.Log;

import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;

Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID

Account account = new Account(client);

account.createPasskey(
"<PASSKEY_ID>", // passkeyId (optional)
"<NAME>", // name (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}

Log.d("Appwrite", result.toString());
})
);

```
26 changes: 26 additions & 0 deletions docs/examples/java/account/delete-passkey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
```java
import android.util.Log;

import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;

Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID

Account account = new Account(client);

account.deletePasskey(
"<PASSKEY_ID>", // passkeyId
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}

Log.d("Appwrite", result.toString());
})
);

```
26 changes: 26 additions & 0 deletions docs/examples/java/account/get-passkey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
```java
import android.util.Log;

import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;

Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID

Account account = new Account(client);

account.getPasskey(
"<PASSKEY_ID>", // passkeyId
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}

Log.d("Appwrite", result.toString());
})
);

```
27 changes: 27 additions & 0 deletions docs/examples/java/account/list-passkeys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
```java
import android.util.Log;

import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;

Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID

Account account = new Account(client);

account.listPasskeys(
List.of(), // queries (optional)
false, // total (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}

Log.d("Appwrite", result.toString());
})
);

```
27 changes: 27 additions & 0 deletions docs/examples/java/account/update-passkey-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
```java
import android.util.Log;

import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;

Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID

Account account = new Account(client);

account.updatePasskeyToken(
"<CHALLENGE_ID>", // challengeId
Map.of("a", "b"), // credential
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}

Log.d("Appwrite", result.toString());
})
);

```
27 changes: 27 additions & 0 deletions docs/examples/java/account/update-passkey-verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
```java
import android.util.Log;

import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;

Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID

Account account = new Account(client);

account.updatePasskeyVerification(
"<PASSKEY_ID>", // passkeyId
Map.of("a", "b"), // credential
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}

Log.d("Appwrite", result.toString());
})
);

```
Loading
Loading