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() {
fun navigate(
navController: NavController,
currentScreen: MutableState<Screen>,
nextScreen: Screen, args: List<Any>?
nextScreen: Screen, args: List<Any>?,
isSearchBarShowing: MutableState<Boolean>
) {
if (nextScreen != currentScreen.value) {
if (nextScreen == Screen.Characters || nextScreen == Screen.Favorite) {
......@@ -71,6 +72,7 @@ fun navigate(
navController.navigateUp()
}
}
isSearchBarShowing.value = false
}
@Composable
......@@ -121,7 +123,7 @@ fun MainScreen() {
// onScreenSelected perform all navigation in the app
val onSelectScreen = remember {
{ 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
import cz.fel.barysole.ackeetesttask.db.room.dao.CharacterDao
import cz.fel.barysole.ackeetesttask.model.CharacterInfo
import kotlinx.coroutines.flow.Flow
import retrofit2.HttpException
import java.io.IOException
import javax.inject.Inject
import javax.inject.Singleton
......@@ -58,9 +60,15 @@ class CharacterRepositoryImpl @Inject constructor(
}
override suspend fun getCharacter(characterId: Long): CharacterInfo? {
val response = rickAndMortyApi.getCharacterById(characterId)
appDatabase.withTransaction {
insertCharacterItems(listOf(response), appDatabase.characterDao())
try {
val response = rickAndMortyApi.getCharacterById(characterId)
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)
}
......
......@@ -34,7 +34,8 @@ sealed class Screen(
"characterdetail/{characterId}",
routeWithoutArgument = "characterdetail",
actionList = listOf(ScreenAction.AddToFavoriteScreenAction),
showBottomBar = false
showBottomBar = false,
isBackButtonShowing = true
)
}
......
......@@ -12,6 +12,8 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
......@@ -30,43 +32,43 @@ fun CharacterListScreen(
Surface(
color = MaterialTheme.colorScheme.background
) {
characterListViewModel.charactersPDFlow.collectAsLazyPagingItems().let {
if (it.loadState.mediator?.refresh is LoadState.Error || it.loadState.mediator?.append is LoadState.Error) {
characterListViewModel.showLoadingError(true)
} else {
characterListViewModel.showLoadingError(false)
val characterItems = characterListViewModel.charactersPDFlow.collectAsLazyPagingItems()
if (characterItems.loadState.source.append is LoadState.Error) {
characterListViewModel.showLoadingError(true)
} else {
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) {
val errorMessage = stringResource(R.string.data_cannot_be_loaded_error)
LaunchedEffect(snackbarHostState) {
snackbarHostState.showSnackbar(
message = errorMessage
}
// todo: there is no pullToRefresh in MD3 at this moment. Add it later. :c
if (characterItems.itemCount == 0 && characterItems.loadState.refresh != LoadState.Loading) {
Column(
modifier = Modifier
.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(
onClick = { characterListViewModel.refreshList() },
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.primary
)
) {
Text(stringResource(R.string.reload_text))
}
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(
characterSearchViewModel.onFieldClean()
}
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,
onQueryChange = { characterSearchViewModel.onCharacterSearch(it) },
onSearch = { characterSearchViewModel.onCharacterSearch(it) },
......@@ -77,31 +79,30 @@ fun MySearchBar(
tonalElevation = 0.dp,
colors = if (isSearchBarIsActive.value) SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.background) else SearchBarDefaults.colors()
) {
characterSearchViewModel.pagingDataFlow.collectAsLazyPagingItems().let {
// do not show elements when the query is empty
if (searchBarState.query.isNotBlank()) {
if (it.loadState.refresh is LoadState.Error) {
Box(
modifier = Modifier
.fillMaxWidth(1f)
.padding(vertical = 64.dp),
contentAlignment = Alignment.Center
) {
Text(
stringResource(R.string.data_cannot_be_loaded_error),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center
)
}
} else {
CharacterList(it) { characterId ->
onSelectScreen(
Screen.CharacterDetail,
listOf(characterId)
)
}
val characterListItems = characterSearchViewModel.pagingDataFlow.collectAsLazyPagingItems()
// do not show elements when the query is empty
if (searchBarState.query.isNotBlank()) {
if (characterListItems.loadState.refresh is LoadState.Error) {
Box(
modifier = Modifier
.fillMaxWidth(1f)
.padding(vertical = 64.dp),
contentAlignment = Alignment.Center
) {
Text(
stringResource(R.string.data_cannot_be_loaded_error),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center
)
}
} else {
CharacterList(characterListItems) { characterId ->
onSelectScreen(
Screen.CharacterDetail,
listOf(characterId)
)
}
}
}
}
}
\ No newline at end of file
}