how to refresh recycyerview when pulldown recycyerview by user - pull

i want refresh retrofit request when user pulldown the page .
i add an icon in option menu when click on icon refresh the data .
GlobalScope.launch {
val result = quotesApi.getQuotes()
withContext(Dispatchers.Main) {
if (result != null)
// Checking the results
prg.visibility = View.GONE
Log.d("T", result.body().toString())
}
}
val pullToRefresh = findViewById<SwipeRefreshLayout>(R.id.pullToRefresh)
pullToRefresh.setOnRefreshListener {
refreshData() // your code
pullToRefresh.isRefreshing = false
}
}
private fun refreshData() {
val quotesApi = RetrofitHelper

Related

How to clear the whole backstack for all nested NavGraphs?

I have an application with a NavigationBar that includes three destinations. It is configured so that two of the destinations point to a seperate nested NavGraph, namely IssuesGraph and InstructionsGraph. While navigating within the nested graphs and then switching between the two destinations, the respective stack for each nested graph is saved as I wish.
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
bottomNavigationItems.forEachIndexed { index, item ->
NavigationBarItem(
icon = { Icon(item.imageResource, stringResource(id = item.titleResource)) },
label = { Text(stringResource(id = item.titleResource)) },
selected = currentDestination?.hierarchy?.any { it.route == item.route } == true,
onClick = {
navController.navigate(item.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
}
However, I now need to reset the complete navigation graph, so I want the App to display the first destination within the IssuesGraph, and if I navigate to the InstructionsGraph, it should show its first destination, too.
My NavHost looks like this:
NavHost(
modifier = modifier,
navController = navController,
startDestination = startDestination,
) {
issueGraph(navController, issuesVM)
instructionsGraph(navController, instructionsVM)
composable(Screen.SettingsScreen.route) {
SettingsScreen(
onSave = {
// Here, I want to trigger a reset of
// the backstacks of issuesGraph and instructionsGraph
// and navigate to the first screen of the issuesGraph
navController.navigate(Screen.IssuesGraph.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = false
}
}
}
)
}
}
The seperate NavGraphs are built using the NavGraphBuilder extension function:
fun NavGraphBuilder.issueGraph(navController: NavController, issuesVM: IssuesVM) {
navigation(startDestination = Screen.IssuesHome.route, Screen.IssuesGraph.route) {
composable(Screen.IssuesHome.route) {
//...
}
}
}
The problem is, that calls like this
navController.navigate(Screen.IssuesGraph.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = false
}
}
only clear the back stack for the nested NavGraph the the goal route Screen.IssuesGraph.route resides in. So in my case, the IssuesGraph is reset, but the InstructionsGraph isn't. When I navigate there, it still shows its previous backstack.
I already stumbled across the setGraph documentation, but couldn't understand how I could use it in my case.
Thanks for any efforts!

Load next data when on click of button using the Paging 3 library for Compose

Currently Paging library handles when to load the data automatically when a user scrolls down. But what if you want to give the user full authority for when they want the next page of data to be loaded i.e when button is clicked show next page of movies. How can you handle this in Paging library? See below how I've implemented the paging to load data as a user scrolls down
Here below this how I implemented the Paging to load next page when user scrolls down
class MoviesPagingDataSource(
private val repo: MoviesRepository,
) : PagingSource<Int, Movies>() {
override fun getRefreshKey(state: PagingState<Int, Movies>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movies> {
return try {
val nextPageNumber = params.key ?: 0
val response = repo.getMovies(page = nextPageNumber, size = 10)
LoadResult.Page(
data = response.content,
prevKey = null,
nextKey = if (response.content.isNotEmpty()) response.number + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
This is how I emit the state in ViewModel for the UI to observe
#HiltViewModel
class MoviesViewModel #Inject constructor(
private val moviesRepository: MoviesRepository
): ViewModel {
....
//emitting the data to the UI to observe
val moviesPagingDataSource = Pager(PagingConfig(pageSize = 10)) {
MoviesPagingDataSource(moviesRepository)
}.flow.cachedIn(viewModelScope)
}
How I'm observing it in the UI
#Composable
fun MoviesList(viewModel: MoviesViewModel) {
val moviesList = viewModel.moviesPagingDataSource.collectAsLazyPagingItems()
LazyColumn {
items(moviesList) { item ->
item?.let { MoviesCard(movie = it) }
}
when (moviesList.loadState.append) {
is LoadState.NotLoading -> Unit
LoadState.Loading -> {
item {
LoadingItem()
}
}
is LoadState.Error -> {
item {
ErrorItem(message = "Some error occurred")
}
}
}
when (moviesList.loadState.refresh) {
is LoadState.NotLoading -> Unit
LoadState.Loading -> {
item {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Center
) {
CircularProgressIndicator()
}
}
}
is LoadState.Error -> TODO()
}
}
}
So currently I'm adding 1 to the previous page every time a user clicks the button to load more movies then saving this movies to the list state. Also making sure the current page is greater than or equal to total pages before loading more data and adding to the list state of previous loaded movies
you can use Channel to block LoadResult returning from load, when user clicks the button, send an element to the Channel. here is the simple
class MoviesPagingDataSource(
private val repo: MoviesRepository
) : PagingSource<Int, Movies>() {
private val channel: Channel<Unit> = Channel(1, BufferOverflow.DROP_LATEST)
override fun getRefreshKey(state: PagingState<Int, Movies>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movies> {
return try {
val nextPageNumber = params.key ?: 0
// load initial page
if (nextPageNumber == 0) loadNextPage()
val response = repo.getMovies(page = nextPageNumber, size = 10)
/*
* block next page data return, when user click the button,
* call loadNextPage. if you don't want to automatically
* request data as the user scrolls toward the end of the
* loaded data. put this line above the repo.getMovies.
**/
channel.receive()
LoadResult.Page(
data = response.content,
prevKey = null,
nextKey = if (response.content.isNotEmpty()) response.number + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
suspend fun loadNextPage() {
channel.send(Unit)
}
}

Jetpack Compose: Bottom bar navigation not responding after deep-linking

I have setup a bottom bar in my new Jetpack Compose app with 2 destinations. I have tried to follow the samples from Google.
So for example it looks something like this:
#Composable
fun MyBottomBar(navController: NavHostController) {
val items = listOf(
BottomNavigationScreen.ScreenA,
BottomNavigationScreen.ScreenB
)
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
BottomNavigation {
items.forEach { screen ->
BottomNavigationItem(
onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
icon = { Icon(imageVector = screen.icon, contentDescription = null) },
label = { Text(stringResource(screen.label)) }
)
}
}
}
This all works fine and I'm able to navigate between the two destinations. However, I also have a deep-link to ScreenB. Once this has been invoked, pressing the ScreenA button seems to do nothing (If I add logging I can see that currentDestination is being repeatedly set to ScreenB) but pressing back returns to the startDestination of ScreenA.
My workaround at the moment is to remove the restoreState = true line from the sample code.
My suspicion is that something about the deep-link is being persisted and although it tries to go to ScreenA the navigation component says that it's got a deep-link pointing to ScreenB so it just goes there. I've tried resetting the activity intent so that it has no flags and no data in the intent, I've even tried changing the intent action type but all to no avail.
I am using Compose 1.0.0-rc02 and Compose Navigation 2.4.0-alpha04.
Am I doing something wrong or is this a bug?
I know you got this code from the official documentation, but I don't think it works well for bottom navigation. It keeps the elements in the navigation stack, so pressing the back button from ScreenB will take you back to ScreenA, which does not seem to me to be the right behavior in this case.
That's why it's better to remove all elements from the stack, so that only one of the tabs is always left. And with saveState you won't lose state anyway. This can be done as follows:
fun NavHostController.navigateBottomNavigationScreen(screen: BottomNavigationScreen) = navigate(screen.route) {
val navigationRoutes = BottomNavigationScreen.values()
.map(BottomNavigationScreen::route)
val firstBottomBarDestination = backQueue
.firstOrNull { navigationRoutes.contains(it.destination.route) }
?.destination
if (firstBottomBarDestination != null) {
popUpTo(firstBottomBarDestination.id) {
inclusive = true
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
And use it like this:
BottomNavigationItem(
onClick = {
navController.navigateBottomNavigationScreen(screen)
},
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
icon = { Icon(imageVector = screen.icon, contentDescription = null) },
label = { Text(screen.label) }
)
For the same reasons, I wouldn't use deep link navigation in this case. Instead, you process them manually. You can use a view model to not re-process deep link if you leave bottom navigation view and come back:
class DeepLinkProcessingViewModel : ViewModel() {
private var deepLinkProcessed = false
fun processDeepLinkIfAvailable(context: Context): String? {
if (!deepLinkProcessed) {
val activity = context.findActivity()
val intentData = activity?.intent?.data?.toString()
deepLinkProcessed = true
return intentData
}
return null
}
}
And using this view model you can calculate start destination like this:
val context = LocalContext.current
val deepLinkProcessingViewModel = viewModel<DeepLinkProcessingViewModel>()
val startDestination = rememberSaveable(context) {
val deepLink = deepLinkProcessingViewModel.processDeepLinkIfAvailable(context)
if (deepLink == "example://playground") {
// deep link handled
BottomNavigationScreen.ScreenB.route
} else {
// default start destination
BottomNavigationScreen.ScreenA.route
}
}
NavHost(navController = navController, startDestination = startDestination) {
...
}
Or, if you have several navigation elements in front of the bottom navigation and you don't want to lose them with a deep link, you can do it as follows:
val navController = rememberNavController()
val context = LocalContext.current
val deepLinkProcessingViewModel = viewModel<DeepLinkProcessingViewModel>()
LaunchedEffect(Unit) {
val deepLink = deepLinkProcessingViewModel.processDeepLinkIfAvailable(context) ?: return#LaunchedEffect
if (deepLink == "example://playground") {
navController.navigateBottomNavigationScreen(BottomNavigationScreen.ScreenB)
}
}
NavHost(
navController = navController,
startDestination = BottomNavigationScreen.ScreenA.route
) {
findActivity:
fun Context.findActivity(): Activity? = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
Looks like it's finally fixed in the 2.4.0-beta02 release; so it was a bug after all.
I was able to add the saveState and restoreState commands back into my BottomBar (as per the documentation) and following a deep-link I was now still able to click the initial destination.

Hide/Show CircularProgressIndicator onclick Jetpack Compose

I want to show CircularProgressIndicator onclick and then hide !the CircularProgressIndicator when the function is executed see code below
var loader by remember {mutableStateOf(false)}
if(loader){
CircularProgressIndicator()
}
Button (onclick {
loader = !loader // show CircularProgressIndicator
// Function which is to be executed
// After function is executed which usually takes 2 sec then after that CircularProgressIndicator should be hidden
loader = loader // Not hidding
}){
Text("Toggle")
}
Basically you need to check that in any case at the end of your processing firebase = false should be called. I'd advise you at least to move the logic into a separate function, so I'll be harder to make a mistake, but basically it should look something like this:
OutlinedButton(onClick = {
keyboardController?.hide()
if (emailState.value.isEmpty() && secureState.value.isEmpty())
return#OutlinedButton
firebase = true
FirebaseAuth.getInstance()
.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
val exception = task.exception?.localizedMessage.toString()
if (task.isSuccessful)
FirebaseAuth.getInstance().currentUser?.sendEmailVerification()
?.addOnCompleteListener { task ->
firebase = true
if (task.isSuccessful)
snackbarCoroutineScope.launch {
scaffoldState.snackbarHostState.showSnackbar(
"Check your Email for Verification")
}
}
else
snackbarCoroutineScope.launch {
scaffoldState.snackbarHostState.showSnackbar(exception)
firebase = true
}
}
}) {
Text(text = "Sign Up")
}
Spacer(modifier = Modifier.size(16.dp))
TextButton(onClick = {
keyboardController?.hide()
if (emailState.value.isEmpty())
return#TextButton
FirebaseAuth.getInstance().sendPasswordResetEmail(email)
.addOnCompleteListener { task ->
val exception = task.exception?.localizedMessage.toString()
firebase = false
if (task.isSuccessful)
snackbarCoroutineScope.launch {
scaffoldState.snackbarHostState.showSnackbar("Check your Email for Password reset")
firebase = true
}
else
snackbarCoroutineScope.launch {
scaffoldState.snackbarHostState.showSnackbar(exception)
firebase = true
}
}
}) {
Text(text = "Forgot password?")
}

How to do update the list view by changing the drop-down item

I want to change my list as I change my drop-down item..The list is dynamic so as I changed my option from drop-down..my list should update which will change only when that API that is called to form a list is changed by the selected drop-down item...The whole url will be same ,the only change is in the item.
Future<ParsedDataModel> fetchTicketListData(String queueName) async {
String openURL = "example.com/query=Queue='$queueName'";
// hitting login api
final response3 = await http.get(openURL);
print(response3.body);
if (response3.statusCode == 200 && response3.body.contains("200 Ok"))
{
// parsing plain data to desired model
ParsedDataModel ticketDataModel =
DashBoardViewController().plainDataToParsedModelData(response3.body.trim());
return ticketDataModel;
} else {
throw Exception('There was a problem');
}
}
Future<List<QueueModel>> fetchTicketsList() async {
var dataList = await fetchTicketListData('InternalTools');
if (dataList.isNoResult) {
throw Exception('No result found.');
} else {
List<QueueModel> openTicketList = makeTicketList(dataList);
openTicketsList = openTicketList;
return openTicketsList;
}
}
// function to parse the data into list of (QueueModel)
List<QueueModel> makeTicketList(ParsedDataModel parsedData) {
return
DashBoardViewController().parsedDataToQueueModelData(parsedData.dataList);
}

Resources