Part 8: Final part of making an App

Mohit Damke
4 min readJul 4, 2024

--

Here we will implement navigation for the app screen and much more stuff this will be the final part where we have implemented the whole app

  • First we have to create the Detail Screen ViewModel
package com.example.newsapp.presentation.details

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.newsapp.domain.model.Article
import com.example.newsapp.domain.usecases.news.NewsUseCases
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject


@HiltViewModel
class DetailViewModel @Inject constructor(
private val newsUseCases: NewsUseCases
) : ViewModel() {
var sideEffect by mutableStateOf<String?>(null)
private set

fun onEvent(event: DetailsEvent) {
when (event) {
is DetailsEvent.UpsertDeleteArticle -> {
viewModelScope.launch {
val article = newsUseCases.selectArticle(event.article.url)
if (article == null) {
upsertArticle(event.article)
} else {
deleteArticle(event.article)
}
}
}

is DetailsEvent.RemoveSideEffect -> {
sideEffect = null
}
}
}

private suspend fun deleteArticle(article: Article) {
newsUseCases.deleteArticle(article = article)
sideEffect = "Article is removed form saved"
}

private suspend fun upsertArticle(article: Article) {
newsUseCases.upsertArticle(article = article)
sideEffect = "Article is been saved"
}

}
  • Here we have implemented the Bookmark logic.
  • We have to create bottom navigation items
package com.example.newsapp.presentation.news_navigation.components

import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.annotation.DrawableRes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.newsapp.R
import com.example.newsapp.presentation.Dimens.ExtraSmallPadding2
import com.example.newsapp.ui.theme.NewsAppTheme


@Composable
fun NewsBottomNavigation(
items: List<BottomNavigationItem>,
selectedItem: Int,
onItemClick: (Int) -> Unit
) {
NavigationBar(
modifier = Modifier.fillMaxWidth(),
containerColor = MaterialTheme.colorScheme.background,
tonalElevation = 10.dp
) {
items.forEachIndexed { index, item ->
NavigationBarItem(
selected = index == selectedItem,
onClick = { onItemClick(index) },
icon = {
Column(horizontalAlignment = CenterHorizontally) {
Icon(
painter = painterResource(id = item.icon),
contentDescription = null,
modifier = Modifier.size(30.dp),
)
Spacer(modifier = Modifier.height(ExtraSmallPadding2))
Text(text = item.text, style = MaterialTheme.typography.labelSmall)
}
},
colors = NavigationBarItemDefaults.colors(
selectedIconColor = MaterialTheme.colorScheme.primary,
selectedTextColor = MaterialTheme.colorScheme.primary,
unselectedIconColor = colorResource(id = R.color.body),
unselectedTextColor = colorResource(id = R.color.body),
indicatorColor = MaterialTheme.colorScheme.background
),
)
}
}
}

data class BottomNavigationItem(
@DrawableRes val icon: Int,
val text: String
)


@Preview
@Preview(uiMode = UI_MODE_NIGHT_YES)
@Composable
fun NewsBottomNavigationPreview() {
NewsAppTheme(dynamicColor = false) {
NewsBottomNavigation(items = listOf(
BottomNavigationItem(icon = R.drawable.baseline_home_24, text = "Home"),
BottomNavigationItem(icon = R.drawable.baseline_search_24, text = "Search"),
BottomNavigationItem(icon = R.drawable.baseline_bookmark_border_24, text = "Bookmark"),
), selectedItem = 0, onItemClick = {})
}
}
  • Here we have implemented the bottom bar screens
package com.example.newsapp.presentation.news_navigation

import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.newsapp.R
import com.example.newsapp.domain.model.Article
import com.example.newsapp.presentation.bookmark.BookmarkScreen
import com.example.newsapp.presentation.bookmark.BookmarkViewModel
import com.example.newsapp.presentation.details.DetailViewModel
import com.example.newsapp.presentation.details.DetailsEvent
import com.example.newsapp.presentation.details.DetailsScreen
import com.example.newsapp.presentation.home.HomeScreen
import com.example.newsapp.presentation.home.HomeViewModel
import com.example.newsapp.presentation.navgraph.Route
import com.example.newsapp.presentation.news_navigation.components.BottomNavigationItem
import com.example.newsapp.presentation.news_navigation.components.NewsBottomNavigation
import com.example.newsapp.presentation.search.SearchScreen
import com.example.newsapp.presentation.search.SearchViewModel

@Composable
fun NewsNavigator() {

val bottomNavigationItems = remember {
listOf(
BottomNavigationItem(icon = R.drawable.baseline_home_24, text = "Home"),
BottomNavigationItem(icon = R.drawable.baseline_search_24, text = "Search"),
BottomNavigationItem(icon = R.drawable.baseline_bookmark_border_24, text = "Bookmark"),
)
}

val navController = rememberNavController()
val backStackState = navController.currentBackStackEntryAsState().value
var selectedItem by rememberSaveable {
mutableIntStateOf(0)
}
selectedItem = when (backStackState?.destination?.route) {
Route.HomeScreen.route -> 0
Route.SearchScreen.route -> 1
Route.BookmarkScreen.route -> 2
else -> 0
}

//Hide the bottom navigation when the user is in the details screen
val isBottomBarVisible = remember(key1 = backStackState) {
backStackState?.destination?.route == Route.HomeScreen.route ||
backStackState?.destination?.route == Route.SearchScreen.route ||
backStackState?.destination?.route == Route.BookmarkScreen.route
}


Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = {
if (isBottomBarVisible) {
NewsBottomNavigation(
items = bottomNavigationItems,
selectedItem = selectedItem,
onItemClick = { index ->
when (index) {
0 -> navigateToTab(
navController = navController,
route = Route.HomeScreen.route
)

1 -> navigateToTab(
navController = navController,
route = Route.SearchScreen.route
)

2 -> navigateToTab(
navController = navController,
route = Route.BookmarkScreen.route
)
}
}
)
}
}) {
val bottomPadding = it.calculateBottomPadding()
NavHost(
navController = navController,
startDestination = Route.HomeScreen.route,
modifier = Modifier.padding(bottom = bottomPadding)
) {
composable(route = Route.HomeScreen.route) { backStackEntry ->
val viewModel: HomeViewModel = hiltViewModel()
val articles = viewModel.news.collectAsLazyPagingItems()
HomeScreen(
articles = articles,
navigateToSearch = {
navigateToTab(
navController = navController,
route = Route.SearchScreen.route
)
},
navigateToDetails = { article ->
navigateToDetails(
navController = navController,
article = article
)
}
)
}
composable(route = Route.SearchScreen.route) {
val viewModel: SearchViewModel = hiltViewModel()
val state = viewModel.state.value
OnBackClickStateSaver(navController = navController)
SearchScreen(
state = state,
event = viewModel::onEvent,
navigateToDetails = { article ->
navigateToDetails(
navController = navController,
article = article
)
}
)
}
composable(route = Route.DetailsScreen.route) {
val viewModel: DetailViewModel = hiltViewModel()
if(viewModel.sideEffect != null){
Toast.makeText(LocalContext.current, viewModel.sideEffect, Toast.LENGTH_SHORT).show()
viewModel.onEvent(DetailsEvent.RemoveSideEffect)
}
navController.previousBackStackEntry?.savedStateHandle?.get<Article?>("article")
?.let { article ->
DetailsScreen(
article = article,
event = viewModel::onEvent,
navigateUp = { navController.navigateUp() },
)
}

}
composable(route = Route.BookmarkScreen.route) {
val viewModel: BookmarkViewModel = hiltViewModel()
val state = viewModel.state.value
OnBackClickStateSaver(navController = navController)
BookmarkScreen(
state = state,
navigateToDetails = { article ->
navigateToDetails(
navController = navController,
article = article
)
}
)
}
}
}
}

@Composable
fun OnBackClickStateSaver(navController: NavController) {
BackHandler(true) {
navigateToTab(
navController = navController,
route = Route.HomeScreen.route
)
}
}

private fun navigateToTab(navController: NavController, route: String) {
navController.navigate(route) {
navController.graph.startDestinationRoute?.let { screen_route ->
popUpTo(screen_route) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
}

private fun navigateToDetails(navController: NavController, article: Article) {
navController.currentBackStackEntry?.savedStateHandle?.set("article", article)
navController.navigate(
route = Route.DetailsScreen.route
)
}

Do some changes in the navigation screen NavController and the app will be ready to use

Here is my repository link please check it

If you have not read Part 7 of the series the here is the link below
https://medium.com/@mohitrdamke/part-7-implementing-roomdb-for-the-saving-bookmark-in-local-storage-c8900e8bd9d2

You can visit the introduction article for the news app with the link below
https://medium.com/@mohitrdamke/news-app-clean-code-architecture-paging-room-db-in-android-studio-24919ba7d16a


You can add 50 clap by just pressing on clap icon

Visit my GitHub Repository : https://github.com/mohitdamke/NewsApp
Make sure to follow me on
Link-tree :
https://linktr.ee/MohitDamke01
LinkedIn :
https://www.linkedin.com/in/mohitdamke01

--

--

Mohit Damke

Junior Android Developer | Kotlin | Jetpack | Firebase | Android Studio | MVVM & Clean Code Architecture