From c58bb8fc3484a5c8c1f76a92a944840650822d1a Mon Sep 17 00:00:00 2001 From: "Luke B. Silver" <22452787+loks0n@users.noreply.github.com> Date: Thu, 18 Jan 2024 14:31:56 +0000 Subject: [PATCH 1/2] Merge pull request #529 from appwrite/feat-android-tutorial Feat android tutorial --- src/lib/utils/code.ts | 2 + src/lib/utils/references.ts | 7 +- src/routes/docs/+page.svelte | 2 +- .../docs/quick-starts/android/+page.markdoc | 6 +- .../tutorials/android/step-1/+page.markdoc | 22 +- .../tutorials/android/step-2/+page.markdoc | 48 ++++ .../tutorials/android/step-3/+page.markdoc | 65 ++++++ .../tutorials/android/step-4/+page.markdoc | 213 +++++++++++++++++ .../tutorials/android/step-5/+page.markdoc | 88 +++++++ .../tutorials/android/step-6/+page.markdoc | 125 ++++++++++ .../tutorials/android/step-7/+page.markdoc | 219 ++++++++++++++++++ .../tutorials/android/step-8/+page.markdoc | 10 + 12 files changed, 792 insertions(+), 15 deletions(-) create mode 100644 src/routes/docs/tutorials/android/step-2/+page.markdoc create mode 100644 src/routes/docs/tutorials/android/step-3/+page.markdoc create mode 100644 src/routes/docs/tutorials/android/step-4/+page.markdoc create mode 100644 src/routes/docs/tutorials/android/step-5/+page.markdoc create mode 100644 src/routes/docs/tutorials/android/step-6/+page.markdoc create mode 100644 src/routes/docs/tutorials/android/step-7/+page.markdoc create mode 100644 src/routes/docs/tutorials/android/step-8/+page.markdoc diff --git a/src/lib/utils/code.ts b/src/lib/utils/code.ts index fd2659318f..4ffe8192d0 100644 --- a/src/lib/utils/code.ts +++ b/src/lib/utils/code.ts @@ -24,6 +24,7 @@ import plaintext from 'highlight.js/lib/languages/plaintext'; import graphql from 'highlight.js/lib/languages/graphql'; import http from 'highlight.js/lib/languages/http'; import css from 'highlight.js/lib/languages/css'; +import groovy from 'highlight.js/lib/languages/groovy'; import { Platform } from './references'; const languages = { @@ -58,6 +59,7 @@ const languages = { rb: ruby, cs: csharp, css: css, + groovy: groovy, svelte: xml } as const satisfies Record; diff --git a/src/lib/utils/references.ts b/src/lib/utils/references.ts index e799383cdc..77968bc913 100644 --- a/src/lib/utils/references.ts +++ b/src/lib/utils/references.ts @@ -66,9 +66,9 @@ export const platformMap: Record = { [Platform.ServerRest]: 'REST', sh: 'Shell', js: 'JavaScript', - jsx: 'React', - tsx: 'React', ts: 'TypeScript', + jsx: 'React', + tsx: 'React', typescript: 'TypeScript', dart: 'Dart', java: 'Java', @@ -97,7 +97,8 @@ export const platformMap: Record = { yaml: 'YAML', text: 'Text', vue: 'Vue', - svelte: 'Svelte' + svelte: 'Svelte', + groovy: 'Groovy' }; export const serviceMap: Record = { diff --git a/src/routes/docs/+page.svelte b/src/routes/docs/+page.svelte index 01d2e647e4..200e9a4200 100644 --- a/src/routes/docs/+page.svelte +++ b/src/routes/docs/+page.svelte @@ -479,4 +479,4 @@ :global(.theme-dark) .bg-overlay { background: linear-gradient(to right, #19191c00 0%, #19191c00 10%, #19191c); } - + \ No newline at end of file diff --git a/src/routes/docs/quick-starts/android/+page.markdoc b/src/routes/docs/quick-starts/android/+page.markdoc index 35b2b9ccb7..bb3e718a40 100644 --- a/src/routes/docs/quick-starts/android/+page.markdoc +++ b/src/routes/docs/quick-starts/android/+page.markdoc @@ -88,7 +88,7 @@ Find your project's ID in the **Settings** page. Create a new file `Appwrite.kt` and add the following code to it, replacing `[YOUR_PROJECT_ID]` with your project ID. ```kotlin -package com.example.myapplication +package import android.content.Context import io.appwrite.Client @@ -139,7 +139,7 @@ object Appwrite { Add the following code to `MainActivity.kt`. ```kotlin -package com.example.myapplication +package import android.os.Bundle import androidx.activity.ComponentActivity @@ -151,7 +151,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.* import androidx.compose.ui.text.input.* import androidx.compose.ui.unit.* -import com.example.myapplication.ui.theme.MyApplicationTheme +import .ui.theme.MyApplicationTheme import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { diff --git a/src/routes/docs/tutorials/android/step-1/+page.markdoc b/src/routes/docs/tutorials/android/step-1/+page.markdoc index 29690e52b6..9cd25d5589 100644 --- a/src/routes/docs/tutorials/android/step-1/+page.markdoc +++ b/src/routes/docs/tutorials/android/step-1/+page.markdoc @@ -1,18 +1,24 @@ --- layout: tutorial -title: Coming soon +title: Build an ideas tracker with Android description: Learn to build an Android app with no backend code using an Appwrite backend. framework: Android category: Mobile and native step: 1 -draft: true --- -Improve the docs, add this guide. +**Idea tracker**: an app to track all the side project ideas that you'll start, but probably never finish. +In this tutorial, you will build Idea tracker with Appwrite and Android. -We still don't have this guide in place, but we do have some great news. -The Appwrite docs, just like Appwrite, is completely open sourced. -This means, anyone can help improve them and add new guides and tutorials. +# Concepts {% #concepts %} +This tutorial will introduce the following concepts: -If you see this page, **we're actively looking for contributions to this page**. -Follow our contribution guidelines, open a PR to [our Website repo](https://github.com/appwrite/website), and collaborate with our core team to improve this page. \ No newline at end of file +1. Setting up your first project +2. Authentication +3. Databases and collections +4. Queries and pagination +5. Storage + +# Prerequisites {% #prerequisites %} +1. Basic knowledge of Kotlin and Android development. +2. Have [Android Studio](https://developer.android.com/studio) installed on your computer. \ No newline at end of file diff --git a/src/routes/docs/tutorials/android/step-2/+page.markdoc b/src/routes/docs/tutorials/android/step-2/+page.markdoc new file mode 100644 index 0000000000..6c2bbe39f5 --- /dev/null +++ b/src/routes/docs/tutorials/android/step-2/+page.markdoc @@ -0,0 +1,48 @@ +--- +layout: tutorial +title: Create app +description: Create a Android app project using Appwrite. +step: 2 +--- + +# Create Android project {% #create-android-project %} + +Create a Android app with the Android Studio **New Project** wizard. Select **Empty Activity** as the template. + +# Add dependencies {% #add-dependencies %} + +Install the Android Appwrite SDK. + +Add the following to your dependencies in the `app/build.gradle` file: + +```groovy +implementation("io.appwrite:sdk-for-android:4.0.1") +``` + +In case you need to create OAuth 2 sessions in the future, the following activity needs to be added inside the `` tag, along side the existing `` tags in your [AndroidManifest.xml](https://developer.android.com/guide/topics/manifest/manifest-intro). +Be sure to replace the **** string with your actual Appwrite project ID. +You can find your Appwrite project ID in you project settings screen in your Appwrite Console. + +```xml + + ... + + ... + + + + + + + + + + + +``` + +# Using the emulator {% #using-the-emulator %} + +Run your app on the [Android emulator](https://developer.android.com/studio/run/emulator). + You can use the emulator to test your app on different versions of the Android platform and different screen sizes without the need to use physical devices. +For now, you should see a blank screen. diff --git a/src/routes/docs/tutorials/android/step-3/+page.markdoc b/src/routes/docs/tutorials/android/step-3/+page.markdoc new file mode 100644 index 0000000000..49aae03450 --- /dev/null +++ b/src/routes/docs/tutorials/android/step-3/+page.markdoc @@ -0,0 +1,65 @@ +--- +layout: tutorial +title: Set up Appwrite +description: Initialize Appwrite in your Android project. +step: 3 +--- + +# Create project {% #create-project %} + +Head to the [Appwrite Console](https://cloud.appwrite.io/console). + +{% only_dark %} +![Create project screen](/images/docs/quick-starts/dark/create-project.png) +{% /only_dark %} +{% only_light %} +![Create project screen](/images/docs/quick-starts/create-project.png) +{% /only_light %} + +If this is your first time using Appwrite, create an account and create your first project. + +Then, under **Add a platform**, add an **Android app**. The **Package Name** should be the same as the one you used when creating your app. + +{% only_dark %} +![Add a platform](/images/docs/quick-starts/dark/add-platform.png) +{% /only_dark %} +{% only_light %} +![Add a platform](/images/docs/quick-starts/add-platform.png) +{% /only_light %} + +You can skip optional steps. + +# Initialize Appwrite SDK {% #init-sdk %} + +To use Appwrite in our Android app, we'll need to find our project ID. Find your project's ID in the **Settings** page. + +{% only_dark %} +![Project settings screen](/images/docs/quick-starts/dark/project-id.png) +{% /only_dark %} +{% only_light %} +![Project settings screen](/images/docs/quick-starts/project-id.png) +{% /only_light %} + +Create a new file `services/Appwrite.kt` to hold our Appwrite related code. +Only one instance of the `Client` class should be created per app. +Add the following code to it, replacing `` with your project ID. + +```kotlin +package .services + +import android.content.Context +import io.appwrite.Client + +object Appwrite { + private const val ENDPOINT = "https://cloud.appwrite.io/v1" + private const val PROJECT_ID = "" + + private lateinit var client: Client + + fun init(context: Context) { + client = Client(context) + .setEndpoint(ENDPOINT) + .setProject(PROJECT_ID) + } +} +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/android/step-4/+page.markdoc b/src/routes/docs/tutorials/android/step-4/+page.markdoc new file mode 100644 index 0000000000..704ce2546e --- /dev/null +++ b/src/routes/docs/tutorials/android/step-4/+page.markdoc @@ -0,0 +1,213 @@ +--- +layout: tutorial +title: Add authentication +description: Add Appwrite authentication to you Android app. +step: 4 +--- + +# Creating an account service {% #creating-an-account-service %} + +We can use services to abstract business logic from our views. +Create a service to handle user authentication with a new file `services/AccountService.kt`. + +Add the following code to it. + +```kotlin +package .services + +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.models.User +import io.appwrite.exceptions.AppwriteException +import io.appwrite.services.Account + +class AccountService(client: Client) { + private val account = Account(client) + + suspend fun getLoggedIn(): User>? { + return try { + account.get() + } catch (e: AppwriteException) { + null + } + } + + suspend fun login(email: String, password: String): User>? { + return try { + account.createEmailSession(email, password) + getLoggedIn() + } catch (e: AppwriteException) { + null + } + } + + suspend fun register(email: String, password: String): User>? { + return try { + account.create(ID.unique(), email, password) + login(email, password) + } catch (e: AppwriteException) { + null + } + } + + suspend fun logout() { + account.deleteSession("current") + } +} +``` + +We can now use this service to login, register and logout a user. Integrate the service to the `/services/Appwrite.kt` file. + +Look for `// Add this line 👇` to find where the changes made here. + +```kotlin +object Appwrite { + private const val ENDPOINT = "https://cloud.appwrite.io/v1" + private const val PROJECT_ID = "" + + private lateinit var client: Client + + // Add this line 👇 + internal lateinit var account: AccountService + + fun init(context: Context) { + client = Client(context) + .setEndpoint(ENDPOINT) + .setProject(PROJECT_ID) + + // Add this line 👇 + account = AccountService(client) + } +} +``` + +# Login screen {% #login-screen %} + +Using this service, we can now create a screen to login or register a user. + +Create a new file `ui/screens/UserScreen.kt` and add the following code to it. + +```kotlin +package .ui.screens + +import .services.AccountService +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import io.appwrite.models.User +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun UserScreen( + user: MutableState>?>, + accountService: AccountService +) { + val coroutineScope = rememberCoroutineScope() + var error by remember { mutableStateOf(null) } + + fun onLogin(email: String, password: String) { + coroutineScope.launch { + user.value = accountService.login(email, password) + } + error = if (user.value === null) { + "Unable to login" + } else { + null + } + } + + fun onRegister(email: String, password: String) { + coroutineScope.launch { + user.value = accountService.register(email, password) + } + error = if (user.value === null) { + "Unable to register" + } else { + null + } + } + + fun onLogout() { + coroutineScope.launch { + accountService.logout() + user.value = null + } + } + + var username by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + + if (user.value !== null) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text(text = "Logged in as ${user.value!!.email}") + Button(onClick = { onLogout() }) { + Text("Logout") + } + } + } + + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + TextField( + value = username, + onValueChange = { username = it }, + label = { Text("Email") } + ) + TextField( + value = password, + onValueChange = { password = it }, + label = { Text("Password") }, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Button(onClick = { onLogin(username, password) }) { + Text("Login") + } + Button(onClick = { onRegister(username, password) }) { + Text("Register") + } + } + if (error !== null) { + Text( + text = error!!, + modifier = Modifier.padding(16.dp), + color = androidx.compose.ui.graphics.Color.Red + ) + } + } +} +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/android/step-5/+page.markdoc b/src/routes/docs/tutorials/android/step-5/+page.markdoc new file mode 100644 index 0000000000..27f9243908 --- /dev/null +++ b/src/routes/docs/tutorials/android/step-5/+page.markdoc @@ -0,0 +1,88 @@ +--- +layout: tutorial +title: Add MainActivity +description: Add navigation to your Android application. +step: 5 +--- + +# Creating the MainActivity {% #creating-the-mainactivity %} + +To show the new screen, we need to set up our `MainActivity` class. Open `MainActivity.kt` and update it with following code. +This code sets up our `MainActivity` with a bottom navigation bar, including a **User** screen. + +```kotlin +package + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.runtime.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Person +import androidx.compose.material3.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import io.appwrite.models.User +import .services.Appwrite +import .ui.screens.UserScreen +import .services.AccountService + +enum class Screen { + User +} + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + Appwrite.init(applicationContext) + + setContent { + AppContent(Appwrite.account) + } + } +} + +@Composable +private fun AppBottomBar(screen: MutableState) { + BottomAppBar { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + IconButton(onClick = { screen.value = Screen.User }) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Icon(Icons.Default.Person, contentDescription = "User") + Text("User") + } + } + } + } +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun AppContent(accountService: AccountService) { + val user = remember { mutableStateOf>?>(null) } + val screen = remember { mutableStateOf(Screen.User) } + + LaunchedEffect(screen) { + user.value = accountService.getLoggedIn() + } + + Scaffold(bottomBar = { AppBottomBar(screen) }) { padding -> + Column(modifier = Modifier.padding(padding)) { + when (screen.value) { + Screen.User -> UserScreen(user, accountService) + else -> Text("Ideas screen") + } + } + } +} +``` + +# Test the MainActivity {% #creating-the-mainactivity %} + +Launch the app and you should be able to use the Login screen to register, login, and logout. Confirm your email address is displayed once you are logged in. \ No newline at end of file diff --git a/src/routes/docs/tutorials/android/step-6/+page.markdoc b/src/routes/docs/tutorials/android/step-6/+page.markdoc new file mode 100644 index 0000000000..c0d0813161 --- /dev/null +++ b/src/routes/docs/tutorials/android/step-6/+page.markdoc @@ -0,0 +1,125 @@ +--- +layout: tutorial +title: Add database +description: Add databases and queries to store user data in you Android application. +step: 6 +--- + +# Create collection + +In Appwrite, data is stored as a collection of documents. Create a collection in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas. + +{% only_dark %} +![Create project screen](/images/docs/tutorials/dark/idea-tracker-collection.png) +{% /only_dark %} + +{% only_light %} +![Create project screen](/images/docs/tutorials/idea-tracker-collection.png) +{% /only_light %} + +Create a new collection with the following attributes: + +| Attribute | Type | Required | Size | +|-------------|--------|----------|----------| +| userId | String | Yes | 200 | +| title | String | Yes | 200 | +| description | String | No | 500 | + +{% only_dark %} +![Collection permissions screen](/images/docs/tutorials/dark/idea-tracker-permissions.png) +{% /only_dark %} +{% only_light %} +![Collection permissions screen](/images/docs/tutorials/idea-tracker-permissions.png) +{% /only_light %} + +Navigate to the **Settings** tab of your collection, add the role **Any** and check the **Read** box. +Next, add a **Users** role and give them access to **Create**, **Update** and **Delete** by checking those boxes. + +# Add and remove methods + +Now that you have a collection to hold ideas, we can read and write to it from our app. +Create a new file `services/IdeasService.kt` and add the following code to it. Replace the values for `ideaDatabaseId` and `ideaCollectionId` with the IDs of the database and collection you created in the previous step. + +```kotlin +package .services + +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Query +import io.appwrite.models.Document +import io.appwrite.services.Databases + +class IdeaService(client: Client) { + companion object { + private const val ideaDatabaseId = "" + private const val ideaCollectionId = "" + } + + private val databases = Databases(client) + + suspend fun fetch(): List>> { + return databases.listDocuments( + ideaDatabaseId, + ideaCollectionId, + listOf(Query.orderDesc("\$createdAt"), Query.limit(10)) + ).documents + } + + suspend fun add( + userId: String, + title: String, + description: String + ): Document> { + return databases.createDocument( + ideaDatabaseId, + ideaCollectionId, + ID.unique(), + mapOf( + "userId" to userId, + "title" to title, + "description" to description + ) + ) + } + + suspend fun remove(id: String) { + databases.deleteDocument( + ideaDatabaseId, + ideaCollectionId, + id + ) + } +} +``` + +Update the `services/Appwrite.kt` file to add a new property for the `IdeaService` class. + +Look for `// Add this line 👇` to find where the changes made here. + +```kotlin +package io.appwrite.tutorialforandroid.services + +import android.content.Context +import io.appwrite.Client + +object Appwrite { + private const val ENDPOINT = "https://cloud.appwrite.io/v1" + private const val PROJECT_ID = "" + + private lateinit var client: Client + + // Add this line 👇 + internal lateinit var ideas: IdeaService + internal lateinit var account: AccountService + + fun init(context: Context) { + client = Client(context) + .setEndpoint(ENDPOINT) + .setProject(PROJECT_ID) + + // Add this line 👇 + ideas = IdeaService(client) + account = AccountService(client) + } +} +``` diff --git a/src/routes/docs/tutorials/android/step-7/+page.markdoc b/src/routes/docs/tutorials/android/step-7/+page.markdoc new file mode 100644 index 0000000000..87ee54ce53 --- /dev/null +++ b/src/routes/docs/tutorials/android/step-7/+page.markdoc @@ -0,0 +1,219 @@ +--- +layout: tutorial +title: Create ideas page +description: Add pagination and ordering to you Android application powered by Appwrite Databases. +step: 7 +--- + +Using the `IdeasService`, we can build a screen to submit and view ideas. +Overwrite the contents of `ui/screens/IdeasScreen.kt` with the following code. + +```kotlin +package .screens + + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import io.appwrite.models.Document +import io.appwrite.models.User +import kotlinx.coroutines.launch +import services.IdeaService + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun IdeasScreen( + user: User>?, + ideasService: IdeaService +) { + var ideas by remember { mutableStateOf>>>(listOf()) } + val coroutineScope = rememberCoroutineScope() + + LaunchedEffect(ideasService) { + coroutineScope.launch { + ideas = ideasService.fetch() + } + } + + fun onSubmit(title: String, description: String) { + if (user === null) return + coroutineScope.launch { + ideas = listOf(ideasService.add(user.id, title, description)).plus(ideas) + } + } + + fun onRemove(ideaId: String) { + coroutineScope.launch { + ideas = ideas.filter { idea -> idea.id !== ideaId } + ideasService.remove(ideaId) + } + } + + var title by remember { mutableStateOf("") } + var description by remember { mutableStateOf("") } + + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (user != null) { + TextField( + value = title, + onValueChange = { title = it }, + label = { Text("Title") }, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) + TextField( + value = description, + onValueChange = { description = it }, + label = { Text("Description") }, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) + Button(onClick = { + onSubmit(title, description) + title = "" + description = "" + }) { + Text("Submit") + } + } + + LazyColumn(modifier = Modifier.fillMaxSize()) { + items(ideas) { idea -> + Column(modifier = Modifier.padding(16.dp)) { + Text(text = idea.data["title"]?.toString() ?: "", fontWeight = FontWeight(700)) + Text(text = idea.data["description"]?.toString() ?: "") + if (user?.id === idea.data["userId"]) + Button(onClick = { onRemove(idea.id) }) { + Text("Remove") + } + } + } + } + } +} +``` + +# Update navigation {% #update-navigation %} + +Update `MainActivity.kt` to add the `IdeasScreen` to the navigation bar. + +Look for `// Add this line 👇` to find where the changes made here. + +```kotlin +package + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.runtime.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +// Add this line 👇 +import androidx.compose.material.icons.filled.List +import androidx.compose.material.icons.filled.Person +import androidx.compose.material3.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import io.appwrite.models.User +import .services.Appwrite +import .services.AccountService +import .ui.screens.UserScreen + +// Add this line 👇 +import .services.IdeaService + +// Add this line 👇 +import .ui.screens.IdeasScreen + + +enum class Screen { + User, + // Add this line 👇 + Ideas +} + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + Appwrite.init(applicationContext) + + setContent { + // Update this line 👇 + AppContent(Appwrite.account, Appwrite.ideas) + } + } +} + +// Add navigation with this composable 👇 +@Composable +private fun AppBottomBar(screen: MutableState) { + BottomAppBar { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + IconButton(onClick = { screen.value = Screen.Ideas }) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Icon(Icons.Default.List, contentDescription = "Ideas") + Text("Ideas") + } + } + IconButton(onClick = { screen.value = Screen.User }) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Icon(Icons.Default.Person, contentDescription = "User") + Text("User") + } + } + } + } +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +// Update this line 👇 +private fun AppContent(accountService: AccountService, ideasService: IdeaService) { + val user = remember { mutableStateOf>?>(null) } + // Update this line 👇 + val screen = remember { mutableStateOf(Screen.Ideas) } + + LaunchedEffect(screen) { + user.value = accountService.getLoggedIn() + } + + Scaffold(bottomBar = { AppBottomBar(screen) }) { padding -> + Column(modifier = Modifier.padding(padding)) { + when (screen.value) { + Screen.User -> UserScreen(user, accountService) + // Update this line 👇 + else -> IdeasScreen(user.value, ideasService) + } + } + } +} +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/android/step-8/+page.markdoc b/src/routes/docs/tutorials/android/step-8/+page.markdoc new file mode 100644 index 0000000000..d7d28794b1 --- /dev/null +++ b/src/routes/docs/tutorials/android/step-8/+page.markdoc @@ -0,0 +1,10 @@ +--- +layout: tutorial +title: Next steps +description: View your Android project powered by Appwrite authentication and databases. +step: 8 +--- + +# Test your project {% #test-project %} + +You can now run your project and test it on your Android device or emulator. \ No newline at end of file From 86cc9d34901edbd50c285fdaba5549f93d7c8088 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Tue, 23 Jan 2024 10:38:45 -0500 Subject: [PATCH 2/2] Apply suggestions from code review --- src/routes/docs/tutorials/android/step-5/+page.markdoc | 2 +- src/routes/docs/tutorials/android/step-6/+page.markdoc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/docs/tutorials/android/step-5/+page.markdoc b/src/routes/docs/tutorials/android/step-5/+page.markdoc index 27f9243908..d3dfae0bc2 100644 --- a/src/routes/docs/tutorials/android/step-5/+page.markdoc +++ b/src/routes/docs/tutorials/android/step-5/+page.markdoc @@ -83,6 +83,6 @@ private fun AppContent(accountService: AccountService) { } ``` -# Test the MainActivity {% #creating-the-mainactivity %} +# Test the MainActivity {% #test-the-mainactivity %} Launch the app and you should be able to use the Login screen to register, login, and logout. Confirm your email address is displayed once you are logged in. \ No newline at end of file diff --git a/src/routes/docs/tutorials/android/step-6/+page.markdoc b/src/routes/docs/tutorials/android/step-6/+page.markdoc index c0d0813161..a43f091652 100644 --- a/src/routes/docs/tutorials/android/step-6/+page.markdoc +++ b/src/routes/docs/tutorials/android/step-6/+page.markdoc @@ -5,7 +5,7 @@ description: Add databases and queries to store user data in you Android applica step: 6 --- -# Create collection +# Create collection {% #create-collection %} In Appwrite, data is stored as a collection of documents. Create a collection in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas. @@ -35,7 +35,7 @@ Create a new collection with the following attributes: Navigate to the **Settings** tab of your collection, add the role **Any** and check the **Read** box. Next, add a **Users** role and give them access to **Create**, **Update** and **Delete** by checking those boxes. -# Add and remove methods +# Add and remove methods {% #add-add-remove-methods %} Now that you have a collection to hold ideas, we can read and write to it from our app. Create a new file `services/IdeasService.kt` and add the following code to it. Replace the values for `ideaDatabaseId` and `ideaCollectionId` with the IDs of the database and collection you created in the previous step.