Build an ideas tracker with Android

4

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 <YOUR_ROOT_PACKAGE_HERE>.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<Map<String, Any>>? {
        return try {
            account.get()
        } catch (e: AppwriteException) {
            null
        }
    }

    suspend fun login(email: String, password: String): User<Map<String, Any>>? {
        return try {
            account.createEmailPasswordSession(email, password)
            getLoggedIn()
        } catch (e: AppwriteException) {
            null
        }
    }

    suspend fun register(email: String, password: String): User<Map<String, Any>>? {
        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 = "<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

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 <YOUR_ROOT_PACKAGE_HERE>.ui.screens

import <YOUR_ROOT_PACKAGE_HERE>.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<User<Map<String, Any>>?>,
    accountService: AccountService
) {
    val coroutineScope = rememberCoroutineScope()
    var error by remember { mutableStateOf<String?>(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
            )
        }
    }
}