marazmone / VocaFlow

VocaFlow: Unlock the World of Words!

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

VocaFlow

VocaFlow is a powerful language learning application built using Kotlin Multiplatform technology. Designed to assist language learners in expanding their vocabulary, VocaFlow provides an immersive and interactive experience for users to learn words and phrases in various languages.

With VocaFlow, language learners can explore a wide range of languages and effortlessly build their vocabulary through engaging exercises and interactive quizzes. The application leverages the Kotlin Multiplatform framework, allowing it to run seamlessly on multiple platforms, including Android, iOS.

Language Diversity: VocaFlow offers an extensive collection of languages to learn from, catering to both popular languages and niche dialects. Users can choose their desired language pairings and easily switch between them to broaden their linguistic horizons.

Vocabulary Expansion: VocaFlow provides users with a comprehensive database of words and phrases specific to each language. Through intuitive exercises and quizzes, learners can practice and reinforce their understanding of vocabulary in context.

Application Scope

Intro Flow

splash-screen intro-screen-1 intro-screen-2 intro-screen-3

Auth Flow

auth-screen-1 auth-screen-2 auth-screen-3

To be continue...

Tech-Stack

  • Tech-stack
    • 100% Kotlin
      • Coroutines - perform background operations
      • Kotlin Flow - data flow across all app layers, including views
      • Kotlin multiplatform - The Kotlin Multiplatform technology is designed to simplify the development of cross-platform projects.
      • Compose Multiplatform - Develop stunning shared UIs for Android, iOS, desktop, and web.
    • Koin - dependency injection (dependency retrieval)
    • Voyager - A multiplatform navigation library built for, and seamlessly integrated with, Jetpack Compose
    • Napier - Napier is a logger library for Kotlin Multiplatform
    • InsetX - InsetX is a Kotlin Multiplatform library for managing window insets
  • Modern Architecture
  • UI
  • CI ...
  • Testing ...
  • Static analysis tools (linters)
    • Detekt - verify code complexity and code smells
  • Gradle
  • GitHub Boots ...

Architecture

Layers

architecture

Presentation Layer

This layer is closest to what the user sees on the screen.

The presentation layer uses MVI pattern.

MVI - action modifies the common UI state and emits a new state to a view via MutableState

The common state is a single source of truth for each view. This solution derives from Unidirectional Data Flow and Redux principles.

Components:

  • View (Composable Function) - observes common view state (through MutableState). Pass user interactions to ViewModel. Views are hard to test, so they should be as simple as possible.
  • ViewModel - emits (through MutableState) view state changes to the view and deals with user interactions (these view models are not simply POJO classes).
  • UIModel - sometimes you may not have enough DomainModel to display UI states. For this purpose, the UIModel will be used, supplementing the domain model with the necessary attributes.
  • Mapper - maps domain model to ui model (to keep presentation layer independent from the domain layer).
  • State - data class that holds the state content of the corresponding screen e.g. list of User, loading status etc. The state is exposed as a Compose runtime MutableState object from that perfectly matches the use-case of receiving continuous updates with initial value.
  • Action - plain object that is sent through ViewModel to the reducer. Actions should reflect UI events caused by the user. Actions provide by reducer and should update state.

For example(ViewModel):

...
	override fun setInitialState(): State = State()
    
	override fun onReduceState(action: Action): State = when (action) {  
		is Loading -> currentState.copy(  
			isLoading = true,  
			isError = false,  
		)  
      
		is GetCurrentUser -> currentState.copy(  
			isLoading = false,  
			isError = false,  
			currentUser = action.currentUser,  
		)  
      
		is Action.Error -> currentState.copy(  
			isLoading = false,  
			isError = true,  
			errorMessage = action.errorMessage,  
		)  
	}
...
  • Effect - plain object that signals one-time side-effect actions that should impact the UI e.g. triggering a navigation action, showing a Toast, SnackBar etc. Effects are exposed as ChannelFlow which behave as in each event is delivered to a single subscriber. An attempt to post an event without subscribers will suspend as soon as the channel buffer becomes full, waiting for a subscriber to appear.

Every screen defines its own contract class that states all corresponding core components described above: state, actions and effects.

For example:

class FirebaseTestContract {  
      
	data class State(  
		val currentUser: FirebaseUser? = null,  
		val isLoading: Boolean = false,  
		val isError: Boolean = false,  
		val errorMessage: String = "",  
	) : BaseViewState  
      
	sealed interface Action : BaseViewAction {  
      
		object Loading : Action  
      
		data class GetCurrentUser(val currentUser: FirebaseUser?) : Action  
      
		data class Error(val errorMessage: String) : Action  
	}  
      
	sealed interface Effect : BaseViewEffect {  
      
		object NavigationBack : Effect  
	}  
}

Domain Layer

  • UseCase - contains business logic
  • DomainModel - defines the core structure of the data that will be used within the application. This is the source of truth for application data.
  • Repository interface - required to keep the domain layer independent from the data layer (Dependency inversion).

Data Layer

Encapsulates application data. Provides the data to the domain layer eg. retrieves data from the internet and cache the data in disk cache (when the device is offline).

Components:

  • Repository is exposing data to the domain layer. Depending on the application structure and quality of the external API repository can also merge, filter, and transform the data. These operations intend to create a high-quality data source for the domain layer. It is the responsibility of the Repository (one or more) to construct Domain models by reading from the Data Source and accepting Domain models to be written to the Data Source
  • Mapper - maps data model to domain model (to keep domain layer independent from the data layer).
  • Data Sources - This application has two Data Sources - remote (used for network access) and cache (local storage used to access device persistent memory). These data sources can be treated as an implicit sub-layer.

Dependency Management

Gradle versions catalog is used as a centralized dependency management third-party dependency coordinates (group, artifact, version) are shared across all modules (Gradle projects and subprojects).

All of the dependencies are stored in the settings.gradle.kts file (default location). Gradle versions catalog consists of a few major sections:

  • [versions] - declare versions that can be referenced by all dependencies
  • [libraries] - declare the aliases to library coordinates
  • [bundles] - declare dependency bundles (groups)
  • [plugins] - declare Gradle plugin dependencies

🌐 Localization

Crowdin

πŸ‡¬πŸ‡§ πŸ‡ΊπŸ‡¦

The approach for using localization in KMM takes from Medium

Example:

Text(
    text = getString(id = "name"),
)
Text(
    text = getString(id = "number", quantity = 1),
)
Text(
    text = getString(id  = "text_by_args", args = arrayOf("1", "2", "3")),
)

To be continue...

About

VocaFlow: Unlock the World of Words!

License:Apache License 2.0


Languages

Language:Kotlin 99.1%Language:Swift 0.7%Language:Shell 0.1%Language:Ruby 0.1%