Skip to content

Commit

Permalink
Merge pull request #7 from ndiritumichael/docs
Browse files Browse the repository at this point in the history
Update Docs
  • Loading branch information
ndiritumichael authored Jun 7, 2024
2 parents ab3fb75 + 34736a0 commit 6e6fd7b
Show file tree
Hide file tree
Showing 77 changed files with 1,782 additions and 281 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/androidbuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ jobs:
- name: Run ktlintCheck on the codebase
run: ./gradlew ktlintCheck

- name: Build With Gradle
run: ./gradlew build

- name: Run Unit Tests
run: ./gradlew clean test

- name: Run debug Build
run: ./gradlew assembleDebug

- name : Build With Gradle
run: ./gradlew build


- name: Upload a Build Artifact
uses: actions/upload-artifact@v4.3.3
Expand Down
117 changes: 117 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
## CalorieBytez App

This is a simple Android app that uses the [Calorie Ninja](https://calorieninjas.com/api) to display
nutritional data for various food items.



*Pre-requisites*
- Built on A.S JellyFish
- JDK 17
- Access token from CalorieNinja.
- Once received place it in the local.properties file as follows:
``` properties
API_KEY = YourKey
```
To Inject the Key when using CI/CD with github actions , add the key to your projects secrets and extract in to your build workflow:

``` yaml
- name: Get local.properties from secrets
run: echo "${{secrets.LOCAL_PROPERTIES }}" > $GITHUB_WORKSPACE/local.properties
```
## Architecture
This project uses a modularized approach using MVVM with Clean architecture which has the following advantages
- Loose coupling between the code - The code can easily be modified without affecting any or a large part of the app's codebase thus easier to scale the application later on.
- Easier to test code.
- Separation of Concern - Different modules have specific responsibilities making it easier for modification and maintenance.
### Modularization Structure
- `core`
- `data`
- aggregates the data from the network and local database
- `network`
- handles getting data from any server/remote source
- `database`
- handles getting cached device data
- `domain`
- defines the core business logic for reuse
- `app`
- handles Ui logic of the app i.e navigation and use of the bottom bar.
- `feature`
- `Search`
- handles displaying data of queried item combinations
- `Food Details`
- handles displaying all food nutritional details
- `Saved Food`
- handles displaying food saved locally
- `testing`
- Encompasses the core testing functionality of the project



| Modularization Graph |
|--------------------------------------------|
| <img src="docs/graphviz.svg" /> |

### Testing

The app includes unit tests for all modules, Instrumented tests are ran as unit tests with the use of Roboelectric
#### tests screenshots

| Image | desc |
|---|-----------------------------------|
| <img src="docs/viewmodeltests.png" /> | Unit tests for the ViewModels |
| <img src="docs/datalayertests.png" /> | Unit tests for the data layer |
| <img src="docs/networktests.png"/> | Unit tests for the network layer |
| <img src="docs/dbtests.png" /> | Unit tests for the database layer |



## TechStack
### Libraries
* Tech-stack
* [Kotlin](https://kotlinlang.org/) - a modern, cross-platform, statically typed, general-purpose programming language with type inference.
* [Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) - lightweight threads to perform asynchronous tasks.
* [Flow](https://kotlinlang.org/docs/reference/coroutines/flow.html) - a stream of data that emits multiple values sequentially.
* [StateFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow#:~:text=StateFlow%20is%20a%20state%2Dholder,property%20of%20the%20MutableStateFlow%20class.) - Flow APIs that enable flows to emit updated state and emit values to multiple consumers optimally.
* [Dagger Hilt](https://dagger.dev/hilt/) - a dependency injection library for Android built on top of [Dagger](https://dagger.dev/) that reduces the boilerplate of doing manual injection.
* [Jetpack](https://developer.android.com/jetpack)
* [Jetpack Compose](https://developer.android.com/jetpack/compose) - A modern toolkit for building native Android UI
* [Lifecycle](https://developer.android.com/topic/libraries/architecture/lifecycle) - perform actions in response to a change in the lifecycle state.
* [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) - store and manage UI-related data lifecycle in a conscious manner and survive configuration change.
* [Room](https://developer.android.com/training/data-storage/room) - An ORM that provides an abstraction layer over SQLite to allow fluent database access.
* [Timber](https://github.com/JakeWharton/timber) - a highly extensible Android logger.
* [Ktor](https://ktor.io/) - A pure Create asynchronous http client


* Tests
* [JUnit](https://junit.org/junit4/) - a simple framework for writing repeatable tests.
* [MockK](https://github.com/mockk) - mocking library for Kotlin
* [Truth](https://github.com/agoda-com/Kakao) - A fluent assertions library for Android and Java.
* Gradle
* [Gradle Kotlin DSL](https://docs.gradle.org/current/userguide/kotlin_dsl.html) - An alternative syntax for writing Gradle build scripts using Koltin.
* [Version Catalogs](https://developer.android.com/build/migrate-to-catalogs) - A scalable way of maintaining dependencies and plugins in a multi-module project.
* [Convention Plugins](https://docs.gradle.org/current/samples/sample_convention_plugins.html) - A way to encapsulate and reuse common build configuration in Gradle, see [here](https://github.com/daniel-waiguru/WeatherApp/tree/main/build-logic%2Fconvention%2Fsrc%2Fmain%2Fjava)
* Plugins
* [Ktlint](https://github.com/JLLeitschuh/ktlint-gradle) - creates convenient tasks in your Gradle project that run ktlint checks or do code auto format.
* [Spotless](https://github.com/diffplug/spotless) - format Java, groovy, markdown, and license headers using gradle.
* CI/CD
* [GitHub Actions](https://github.com/features/actions)

## ScreenShots
## Screenshots with Descriptions in Columns

| Loading | Recent Searches | Food List |
|---|---|---|
| <img src="docs/loading.jpg" width="250"/> | <img src="docs/recentssearch.jpg" width="250"/> | <img src="docs/foodlist.jpg" width="250"/> |

| Idle | Details | Error Screen |
|---|---|--------------|
| <img src="docs/idle.jpg" width="250"/> | <img src="docs/details.jpg" width="250"/> |<img src="docs/errorscreen.jpg" width="250"/>


2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ android {
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
merges += "META-INF/LICENSE.md"
merges += "META-INF/LICENSE-notice.md"
}
}

Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<application
android:name=".CalorieApplication"
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/devmike/caloriebytez/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.devmike.caloriebytez.ui.navigation.AppNavigation
import com.devmike.caloriebytez.ui.navigation.CalorieBottomBar
import com.devmike.caloriebytez.navigation.AppNavigation
import com.devmike.caloriebytez.navigation.CalorieBottomBar
import com.devmike.caloriebytez.ui.theme.CalorieBytezTheme
import dagger.hilt.android.AndroidEntryPoint

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.devmike.caloriebytez.ui.navigation
package com.devmike.caloriebytez.navigation

import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.devmike.domain.models.AppDestinations
import com.devmike.fooddetails.FoodDetailsScreen
import com.devmike.saveditems.SavedItemsScreen
import com.devmike.search.SearchScreen

@Composable
Expand All @@ -30,7 +30,9 @@ fun AppNavigation(
}

composable<AppDestinations.SavedFood> {
Text("Saved")
SavedItemsScreen { foodName ->
navController.navigate(AppDestinations.FoodDetails(name = foodName))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.devmike.caloriebytez.ui.navigation
package com.devmike.caloriebytez.navigation

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.material3.BottomAppBar
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.devmike.caloriebytez.ui.navigation
package com.devmike.caloriebytez.navigation

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bookmarks
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ plugins {
alias(libs.plugins.detekt)
alias(libs.plugins.spotless)
alias(libs.plugins.android.library) apply false
alias(libs.plugins.module.graph) apply true
}

subprojects {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.devmike.commonui.sharedui

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.SearchOff
import androidx.compose.material.icons.filled.TimerOff
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material.icons.filled.WifiOff
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.devmike.domain.models.AppErrors

@Composable
fun ErrorScreen(
error: AppErrors,
onRetry: () -> Unit,
) {
Column(
modifier =
Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Icon(
imageVector =
when (error) {
is AppErrors.NoInternet -> Icons.Default.WifiOff
is AppErrors.Timeout -> Icons.Default.TimerOff
is AppErrors.Unknown -> Icons.Default.Error
is AppErrors.NotFound -> Icons.Default.SearchOff
is AppErrors.Empty -> Icons.Default.Warning
is AppErrors.Unauthorized -> Icons.Default.Lock
},
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = Color.Red,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = error.message,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onRetry) {
Text(text = "Retry")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ import com.devmike.domain.models.CalorieModel

@Composable
fun FoodList(
modifier: Modifier = Modifier,
ingredients: List<CalorieModel>,
onFoodItemClicked: (name: String) -> Unit,
) {
LazyColumn(
modifier =
Modifier
modifier
.fillMaxWidth()
.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(6.dp),
Expand Down

This file was deleted.

2 changes: 2 additions & 0 deletions core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ dependencies {
api(project(":domain"))
implementation(project(":core:network"))
implementation(project(":core:database"))

testImplementation(project(":core:testing"))
}

This file was deleted.

4 changes: 2 additions & 2 deletions core/data/src/main/java/com/devmike/data/di/DataModule.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.devmike.data.di

import com.devmike.data.repository.CaloriesRepository
import com.devmike.data.repository.CaloriesRepositoryImpl
import com.devmike.data.repository.RecentSearchesRepository
import com.devmike.data.repository.RecentSearchesRepositoryImpl
import com.devmike.domain.repositories.CaloriesRepository
import com.devmike.domain.repositories.RecentSearchesRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand Down
Loading

0 comments on commit 6e6fd7b

Please sign in to comment.