Building a subscription tracker Desktop and iOS app with compose multiplatform

This page summarizes the projects mentioned and recommended in the original post on dev.to

InfluxDB - Power Real-Time Data Analytics at Scale
Get real-time insights from all types of time series data with InfluxDB. Ingest, query, and analyze billions of data points in real-time with unbounded cardinality.
www.influxdata.com
featured
SaaSHub - Software Alternatives and Reviews
SaaSHub helps you find the best software and product alternatives
www.saashub.com
featured
  • kmp-expense-tracker

    Series: Building a subscription tracker Desktop and iOS app with compose multiplatform

    If you want to check out the code, here's the repository: https://github.com/kuroski/kmp-expense-tracker

  • InfluxDB

    Power Real-Time Data Analytics at Scale. Get real-time insights from all types of time series data with InfluxDB. Ingest, query, and analyze billions of data points in real-time with unbounded cardinality.

    InfluxDB logo
  • kdoctor

    Environment analysis tool

    Since we are not building an Android app, you don't actually need to install Android Studio, so it is completely fine if by the end of the documentation you have a kdoctor result like

  • BuildKonfig

    BuildConfig for Kotlin Multiplatform Project

    BuildKonfig + dotenv-gradle we are going to use those plugins to inject our environment variables

  • dotenv-gradle

    A Gradle plugin provides environment variable extension, and supported ".env" file.

    BuildKonfig + dotenv-gradle we are going to use those plugins to inject our environment variables

  • jsr354-ri

    JSR 354 - Moneta: Reference Implementation

  • money-kotlin

    Kotlin extensions for javax.money (Moneta) JSR 354

  • voyager

    🛸 A pragmatic navigation library for Jetpack Compose (by adrielcafe)

    // composeApp/src/commonMain/kotlin/ui/screens/expenses/ExpensesScreenViewModel.kt package ui.screens.expenses import Expense import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.delay import kotlinx.coroutines.launch private val logger = KotlinLogging.logger {} /** * Base state definition for our screen */ data class ExpensesScreenState( val data: List, ) { /** * Computed property to get the avg price of the expenses */ val avgExpenses: String get() = data.map { it.price }.average().toString() } /** * View model of our screen * More about ViewModels below */ class ExpensesScreenViewModel : StateScreenModel( ExpensesScreenState( data = listOf(), ), ) { init { /** * Simulating the "API request" by adding some latency * and fake data */ screenModelScope.launch { logger.info { "Fetching expenses" } delay(3000) mutableState.value = ExpensesScreenState( data = listOf( Expense( id = "1", name = "Rent", icon = "🏠", price = 102573, ), Expense( id = "2", name = "Apple one", icon = "🍎", price = 2595, ), Expense( id = "3", name = "Netflix", icon = "📺", price = 1299, ), ) ) } } } // composeApp/src/commonMain/kotlin/ui/screens/expenses/ExpensesScreen.kt package ui.screens.expenses import Expense import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen import io.github.oshai.kotlinlogging.KotlinLogging import ui.theme.BorderRadius import ui.theme.IconSize import ui.theme.Spacing private val logger = KotlinLogging.logger {} /** * Voyager screen, since there are no params * we can define it as a plain `object` */ object ExpensesScreen : Screen { @Composable override fun Content() { /** * Instantiating our ViewModel * https://voyager.adriel.cafe/screenmodel */ val viewModel = rememberScreenModel { ExpensesScreenViewModel() } /** * More about this below, but for now, differently than JS * we handle values over time with Kotlin coroutine `Flow's` (in this case, `StateFlow`) * you can think of it as something similar to `Observables` in reactive programming */ val state by viewModel.state.collectAsState() val onExpenseClicked: (Expense) -> Unit = { logger.info { "Redirect to edit screen" } } Scaffold( topBar = { CenterAlignedTopAppBar( title = { Text("My subscriptions", style = MaterialTheme.typography.titleMedium) }, ) }, bottomBar = { BottomAppBar( contentPadding = PaddingValues(horizontal = Spacing.Large), ) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, ) { Column { Text( "Average expenses", style = MaterialTheme.typography.bodyLarge, ) Text( "Per month".uppercase(), style = MaterialTheme.typography.bodyMedium, ) } Text( state.avgExpenses, style = MaterialTheme.typography.labelLarge, ) } } }, ) { paddingValues -> Box(modifier = Modifier.padding(paddingValues)) { ExpenseList(state.data, onExpenseClicked) } } } } @Composable private fun ExpenseList( expenses: List, onClick: (expense: Expense) -> Unit, ) { LazyColumn( verticalArrangement = Arrangement.spacedBy(Spacing.Small_100), ) { items( items = expenses, key = { it.id }, ) { expense -> ExpenseListItem( expense = expense, onClick = { logger.info { "Clicked on ${expense.name}" } onClick(expense) }, ) } item { Spacer(Modifier.height(Spacing.Medium)) } } } @Composable private fun ExpenseListItem( expense: Expense, onClick: () -> Unit = {}, ) { Surface( modifier = Modifier .fillMaxWidth() .padding(horizontal = Spacing.Medium) .defaultMinSize(minHeight = 56.dp), onClick = onClick, shape = RoundedCornerShape(BorderRadius.small), color = MaterialTheme.colorScheme.surfaceVariant, ) { Row( modifier = Modifier .padding( horizontal = Spacing.Medium_100, vertical = Spacing.Small_100, ), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(Spacing.Large), ) { Text( text = expense.icon ?: "", fontSize = IconSize.Medium, modifier = Modifier.defaultMinSize(minWidth = 24.dp), ) Text( text = expense.name, style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurfaceVariant), modifier = Modifier.weight(1f), ) Text( text = (expense.price).toString(), style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurfaceVariant), ) } } }

  • SaaSHub

    SaaSHub - Software Alternatives and Reviews. SaaSHub helps you find the best software and product alternatives

    SaaSHub logo
  • compose-cupertino

    Compose Multiplatform UI components for iOS (Cupertino Widgets)

    compose-cupertino which provides compose multiplatform components for iOS

  • jewel

    An implementation of the IntelliJ look and feels in Compose for Desktop

    Jewel which provides IntelliJ look and feels in Compose for Desktop

  • Logback

    The reliable, generic, fast and flexible logging framework for Java.

    Logback we will use mostly with Ktor, this dependency is not required, but it is nice to see logs of requests + it will get rid of some annoying warnings while running the project

  • ktor

    Framework for quickly creating connected applications in Kotlin with minimal effort

    Ktor client to manage HTTP requests

  • koin

    Koin - a pragmatic lightweight dependency injection framework for Kotlin & Kotlin Multiplatform

    Koin to manage dependency injection (more explanation about that later)

  • compose-samples

    Official Jetpack Compose samples.

    Voyager is a multiplatform navigation library built for, and seamlessly integrated with, Jetpack Compose.

NOTE: The number of mentions on this list indicates mentions on common posts plus user suggested alternatives. Hence, a higher number means a more popular project.

Suggest a related project

Related posts

  • Kafka Archives - Text & Audiobooks in the public domain

    2 projects | /r/androiddev | 5 Jun 2023
  • Which layer does a socket belong to?

    2 projects | /r/androiddev | 20 Mar 2023
  • foss dictionary app

    2 projects | /r/fossdroid | 9 Mar 2023
  • 30k lines of SwiftUI in production later

    3 projects | news.ycombinator.com | 28 Jan 2023
  • Rich Text Editor & Markdown

    1 project | /r/androiddev | 23 Jan 2023

Did you konow that Kotlin is
the 15th most popular programming language
based on number of metions?