Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • barysole/ackee-test-task
1 result
Show changes
Commits on Source (2)
## Ackee android test task
This test task that was written based on the following description: https://github.com/AckeeCZ/android-task-rick-and-morty#readme.
In this project, I used the Compose toolkit for defining the UI layer and Coroutines for handling asynchronous tasks. It is the first time I have used these libraries in a project.
Normally, I use default XML layouts and RxJava. You can find an example of my "study" e-shop application with XML/RxJava on https://gitlab.fel.cvut.cz/barysole/pda-sw-e-shop.
The application uses the modern Material Design 3 component library, and because of that, some elements look different from the design. I have decided that it is not a problem for this test task.
## APPLICATION APK
## KNOWING ISSUES
- Problem: The characters list doesn't save its scrolling position after navigating to the character detail screen and back.
Analysis: LazyColumn gets the characters list from the ViewModel flow by using the ".collectAsLazyPagingItems()" method, and this method retrieves only part of the characters list after navigation.
\ No newline at end of file
File added
...@@ -51,7 +51,8 @@ class MainActivity : ComponentActivity() { ...@@ -51,7 +51,8 @@ class MainActivity : ComponentActivity() {
fun navigate( fun navigate(
navController: NavController, navController: NavController,
currentScreen: MutableState<Screen>, currentScreen: MutableState<Screen>,
nextScreen: Screen, args: List<Any>? nextScreen: Screen, args: List<Any>?,
isSearchBarShowing: MutableState<Boolean>
) { ) {
if (nextScreen != currentScreen.value) { if (nextScreen != currentScreen.value) {
if (nextScreen == Screen.Characters || nextScreen == Screen.Favorite) { if (nextScreen == Screen.Characters || nextScreen == Screen.Favorite) {
...@@ -71,6 +72,7 @@ fun navigate( ...@@ -71,6 +72,7 @@ fun navigate(
navController.navigateUp() navController.navigateUp()
} }
} }
isSearchBarShowing.value = false
} }
@Composable @Composable
...@@ -121,7 +123,7 @@ fun MainScreen() { ...@@ -121,7 +123,7 @@ fun MainScreen() {
// onScreenSelected perform all navigation in the app // onScreenSelected perform all navigation in the app
val onSelectScreen = remember { val onSelectScreen = remember {
{ screen: Screen, args: List<Any>? -> { screen: Screen, args: List<Any>? ->
navigate(navController, selectedScreen, screen, args) navigate(navController, selectedScreen, screen, args, isSearchBarShowing)
} }
} }
......
...@@ -10,6 +10,8 @@ import cz.fel.barysole.ackeetesttask.db.room.AppDatabase ...@@ -10,6 +10,8 @@ import cz.fel.barysole.ackeetesttask.db.room.AppDatabase
import cz.fel.barysole.ackeetesttask.db.room.dao.CharacterDao import cz.fel.barysole.ackeetesttask.db.room.dao.CharacterDao
import cz.fel.barysole.ackeetesttask.model.CharacterInfo import cz.fel.barysole.ackeetesttask.model.CharacterInfo
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import retrofit2.HttpException
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
...@@ -58,9 +60,15 @@ class CharacterRepositoryImpl @Inject constructor( ...@@ -58,9 +60,15 @@ class CharacterRepositoryImpl @Inject constructor(
} }
override suspend fun getCharacter(characterId: Long): CharacterInfo? { override suspend fun getCharacter(characterId: Long): CharacterInfo? {
val response = rickAndMortyApi.getCharacterById(characterId) try {
appDatabase.withTransaction { val response = rickAndMortyApi.getCharacterById(characterId)
insertCharacterItems(listOf(response), appDatabase.characterDao()) appDatabase.withTransaction {
insertCharacterItems(listOf(response), appDatabase.characterDao())
}
} catch (exception: IOException) {
//todo: add exception handling
} catch (exception: HttpException) {
//todo: add exception handling
} }
return appDatabase.characterDao().getById(characterId) return appDatabase.characterDao().getById(characterId)
} }
......
...@@ -34,7 +34,8 @@ sealed class Screen( ...@@ -34,7 +34,8 @@ sealed class Screen(
"characterdetail/{characterId}", "characterdetail/{characterId}",
routeWithoutArgument = "characterdetail", routeWithoutArgument = "characterdetail",
actionList = listOf(ScreenAction.AddToFavoriteScreenAction), actionList = listOf(ScreenAction.AddToFavoriteScreenAction),
showBottomBar = false showBottomBar = false,
isBackButtonShowing = true
) )
} }
......
...@@ -12,6 +12,8 @@ import androidx.compose.material3.Surface ...@@ -12,6 +12,8 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
...@@ -30,43 +32,43 @@ fun CharacterListScreen( ...@@ -30,43 +32,43 @@ fun CharacterListScreen(
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
characterListViewModel.charactersPDFlow.collectAsLazyPagingItems().let { val characterItems = characterListViewModel.charactersPDFlow.collectAsLazyPagingItems()
if (it.loadState.mediator?.refresh is LoadState.Error || it.loadState.mediator?.append is LoadState.Error) { if (characterItems.loadState.source.append is LoadState.Error) {
characterListViewModel.showLoadingError(true) characterListViewModel.showLoadingError(true)
} else { } else {
characterListViewModel.showLoadingError(false) characterListViewModel.showLoadingError(false)
}
// If the UI state contains an error, show snackbar
val characterListScreenState by characterListViewModel.uiState.collectAsState()
if (characterListScreenState.showError) {
val errorMessage = stringResource(R.string.data_cannot_be_loaded_error)
LaunchedEffect(snackbarHostState) {
snackbarHostState.showSnackbar(
message = errorMessage
)
} }
// If the UI state contains an error, show snackbar }
if (characterListViewModel.uiState.value.showError) { // todo: there is no pullToRefresh in MD3 at this moment. Add it later. :c
val errorMessage = stringResource(R.string.data_cannot_be_loaded_error) if (characterItems.itemCount == 0 && characterItems.loadState.refresh != LoadState.Loading) {
LaunchedEffect(snackbarHostState) { Column(
snackbarHostState.showSnackbar( modifier = Modifier
message = errorMessage .fillMaxWidth()
.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = { characterListViewModel.refreshList() },
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.primary
) )
}
}
// todo: there is no pullToRefresh in MD3 at this moment. Add it later. :c
if (it.itemCount == 0 && it.loadState.refresh != LoadState.Loading) {
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Button( Text(stringResource(R.string.reload_text))
onClick = { characterListViewModel.refreshList() },
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.primary
)
) {
Text(stringResource(R.string.reload_text))
}
} }
} else {
CharacterList(it, onSelectItem)
} }
} else {
CharacterList(characterItems, onSelectItem)
} }
} }
} }
\ No newline at end of file
...@@ -45,7 +45,9 @@ fun MySearchBar( ...@@ -45,7 +45,9 @@ fun MySearchBar(
characterSearchViewModel.onFieldClean() characterSearchViewModel.onFieldClean()
} }
SearchBar( SearchBar(
modifier = Modifier.padding(if (isSearchBarIsActive.value) 0.dp else 8.dp), modifier = Modifier
.fillMaxWidth(1f)
.padding(if (isSearchBarIsActive.value) 0.dp else 8.dp),
query = searchBarState.query, query = searchBarState.query,
onQueryChange = { characterSearchViewModel.onCharacterSearch(it) }, onQueryChange = { characterSearchViewModel.onCharacterSearch(it) },
onSearch = { characterSearchViewModel.onCharacterSearch(it) }, onSearch = { characterSearchViewModel.onCharacterSearch(it) },
...@@ -77,31 +79,30 @@ fun MySearchBar( ...@@ -77,31 +79,30 @@ fun MySearchBar(
tonalElevation = 0.dp, tonalElevation = 0.dp,
colors = if (isSearchBarIsActive.value) SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.background) else SearchBarDefaults.colors() colors = if (isSearchBarIsActive.value) SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.background) else SearchBarDefaults.colors()
) { ) {
characterSearchViewModel.pagingDataFlow.collectAsLazyPagingItems().let { val characterListItems = characterSearchViewModel.pagingDataFlow.collectAsLazyPagingItems()
// do not show elements when the query is empty // do not show elements when the query is empty
if (searchBarState.query.isNotBlank()) { if (searchBarState.query.isNotBlank()) {
if (it.loadState.refresh is LoadState.Error) { if (characterListItems.loadState.refresh is LoadState.Error) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth(1f) .fillMaxWidth(1f)
.padding(vertical = 64.dp), .padding(vertical = 64.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
stringResource(R.string.data_cannot_be_loaded_error), stringResource(R.string.data_cannot_be_loaded_error),
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} }
} else { } else {
CharacterList(it) { characterId -> CharacterList(characterListItems) { characterId ->
onSelectScreen( onSelectScreen(
Screen.CharacterDetail, Screen.CharacterDetail,
listOf(characterId) listOf(characterId)
) )
}
} }
} }
} }
} }
} }
\ No newline at end of file