Recyclerview list item vanishes after closing and restarting app. KOTLIN - kotlin

I just implemented an undo function for a snackbar in my to do list app. Everything works perfectly until you close the app and open it again. Once it's opened the list item that was just restored/undone does not show up. However if you exit the app and NOT close it. The list item is still there.
override fun onViewSwiped(position: Int) {
val removedItem = list.removeAt(position)
notifyItemRemoved(position)
updateNotesPositionInDb()
val snackBar = Snackbar
.make((context as Activity).findViewById(R.id.mainLayout), "Task deleted", Snackbar.LENGTH_LONG)
.setAction("Undo"){
undoDeleteTask(removedItem.ID)
list.add(position, removedItem)
notifyItemInserted(position)
updateNotesPositionInDb()
}
.addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onShown(transientBottomBar: Snackbar?) {
}
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
deleteTask(removedItem.ID)
updateNotesPositionInDb()
}
})
snackBar.show()
}
fun undoDeleteTask(id: String) {
val db = mHelper.readableDatabase
db.insert(TaskContract.TaskEntry.TABLE, "id=$id", null)
db.close()
}
Let me explain again. When a user swipes the item and they press undo. The item will be added back to the recyclerview. If a user were to exit the app, but not close it. The item will store be there. However if the user were to close the app then open it, the recently restored item is no longer there.

Your Snackbar callback always deletes the item from the database
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
deleteTask(removedItem.ID)
updateNotesPositionInDb()
}
From the docs:
public void onDismissed (B transientBottomBar, int event)
Called when the given BaseTransientBottomBar has been dismissed, either through a time-out, having been manually dismissed, or an action being clicked.
So even if the user clicks that undo action, the dismissed callback runs and always deletes from the DB. You need to check that event parameter in the callback, and make sure it doesn't do the delete operation if it was a click that dismissed it:
int: The event which caused the dismissal. One of either: DISMISS_EVENT_SWIPE, DISMISS_EVENT_ACTION, DISMISS_EVENT_TIMEOUT, DISMISS_EVENT_MANUAL or DISMISS_EVENT_CONSECUTIVE.
I'm guessing just an if (event != DISMISS_EVENT_ACTION) will do it!
The reason you're only seeing this on a restart is that I'm guessing deleteTask(removedItem.ID) doesn't update your in-memory list or the RecyclerView's adapter - your flow here seems to be
remove from the in-memory list
if the user clicks undo then restore it
if they don't then remove it from the DB as well
So what happens here is it's removed from the in-memory list, then it's restored, then the data is removed from the DB. So long as the app stays in memory you'll be able to see the data that's in the list. But as soon as you restart the app, it has to restore the list by reading the DB, and the item isn't in there anymore

Related

How to delete audio file record and remove checked recyclerview item after user clicks 'Yes' on alert dialog

I have an alert dialog that pops up when the user clicks the delete button.
Alert dialog
When the user clicks yes, I want the recyclerview item they selected to be removed and delete the file from storage.
The onClickListener that shows the alert dialog
findViewById<ImageButton>(R.id.btnDelete).setOnClickListener{
val mAlertDialog = AlertDialog.Builder(this#GalleryActivity)
mAlertDialog.setIcon(R.drawable.ic_delete_dialog) //set alertdialog icon
mAlertDialog.setTitle("Delete") //set alertdialog title
mAlertDialog.setMessage("Delete audio?") //set alertdialog message
mAlertDialog.setPositiveButton("Yes") { dialog, id ->
//perform some tasks here
Toast.makeText(this#GalleryActivity, "Deleted", Toast.LENGTH_SHORT).show()
}
mAlertDialog.setNegativeButton("No") { dialog, id ->
//perform som tasks here
Toast.makeText(this#GalleryActivity, "No", Toast.LENGTH_SHORT).show()
}
mAlertDialog.show()
}
Some folks enjoy code to words, so I will do the two
Steps
So let assume you are getting your AudioRecord from local database or remote source, I believe you will already have a function or an endpoints to delete. now let assume the endpoint or database is collecting array of checked item. maybe array of the Id.
So in your
class DeleteRecordFragment: DialogFragment() {
//Define an array collecting list of checked audio let assume
private var checkedAudio : ArrayList<String> = arrayOf()
//Let assume your database to be
private var database = MyDatabase()
as you check each item, you add the ID to > checkedAudio
now time to delete.
inside Yes dialog.
just call the function that delete and pass in the id of the array you are deleting
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let{ it ->
val alertBuilder = AlertDialog.Builder(it)
alertBuilder.setTitle("Delete record")
alertBuilder.setMessage("Are you sure you want to delete?")
alertBuilder.setPositiveButton("Yes",
DialogInterface.OnClickListener{ dialog, id ->
Log.d("delete", "Yes Pressed")
database.deleteSelectedItem(checkedAudio = checkedAudio)
//Now fetch new list from the data source or database.
database.fetchNewAudio()
//for adapter to be aware to sync., you can call
mAdapter.notifyDataSetChanged()
//but if you are using diffUtils, this should sync automatically
}).setNegativeButton("Cancel",
DialogInterface.OnClickListener{dialog, id ->
Log.d("delete", "Cancel Pressed")
})
alertBuilder.create()
} ?: throw IllegalStateException("Exception !! Activity is null !!")
}

How Should I implement onbackpressed() in fragment kotlin [duplicate]

I am using The new Navigation Architecture Component in android and I am stuck in clearing the navigation stack after moving to a new fragment.
Example:
I am in the loginFragment and I want this fragment to be cleared from the stack when I navigate to the home fragment so that the user will not be returned back to the loginFragment when he presses the back button.
I am using a simple NavHostFragment.findNavController(Fragment).navigate(R.id.homeFragment) to navigate.
Current Code :
mAuth.signInWithCredential(credential)
.addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment);
} else {
Log.w(TAG, "signInWithCredential:failure", task.getException());
}
}
});
I tried using the NavOptions in the navigate(), but the back button is still sending me back to the loginFragment
NavOptions.Builder navBuilder = new NavOptions.Builder();
NavOptions navOptions = navBuilder.setPopUpTo(R.id.homeFragment, false).build();
NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment, null, navOptions);
First, add attributes app:popUpTo='your_nav_graph_id' and app:popUpToInclusive="true" to the action tag.
<fragment
android:id="#+id/signInFragment"
android:name="com.glee.incog2.android.fragment.SignInFragment"
android:label="fragment_sign_in"
tools:layout="#layout/fragment_sign_in" >
<action
android:id="#+id/action_signInFragment_to_usersFragment"
app:destination="#id/usersFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/main_nav_graph"
app:popUpToInclusive="true" />
</fragment>
Second, navigate to the destination, using above action as parameter.
findNavController(fragment).navigate(
SignInFragmentDirections.actionSignInFragmentToUserNameFragment())
See the docs for more information.
NOTE: If you navigate using method navigate(#IdRes int resId), you won't get the desired result. Hence, I used method navigate(#NonNull NavDirections directions).
I think your question specifically pertains on how to use the Pop Behavior / Pop To / app:popUpTo (in xml)
In documentation,
Pop up to a given destination before navigating. This pops all non-matching destinations from the back stack until this destination is found.
Example (Simple Job hunting app)
my start_screen_nav graph is like this:
startScreenFragment (start) -> loginFragment -> EmployerMainFragment
-> loginFragment -> JobSeekerMainFragment
if I want to navigate to EmployerMainFragment and pop all including startScreenFragment then the code will be:
<action
android:id="#+id/action_loginFragment_to_employerMainFragment"
app:destination="#id/employerMainFragment"
app:popUpTo="#+id/startScreenFragment"
app:popUpToInclusive="true" />
if I want to navigate to EmployerMainFragment and pop all excluding startScreenFragment then the code will be:
<action
android:id="#+id/action_loginFragment_to_employerMainFragment"
app:destination="#id/employerMainFragment"
app:popUpTo="#+id/startScreenFragment"/>
if I want to navigate to EmployerMainFragment and pop loginFragment but not startScreenFragment then the code will be:
<action
android:id="#+id/action_loginFragment_to_employerMainFragment"
app:destination="#id/employerMainFragment"
app:popUpTo="#+id/loginFragment"
app:popUpToInclusive="true"/>
OR
<action
android:id="#+id/action_loginFragment_to_employerMainFragment"
app:destination="#id/employerMainFragment"
app:popUpTo="#+id/startScreenFragment"/>
In my case i needed to remove everything in the back Stack before i open a new fragment so i used this code
navController.popBackStack(R.id.fragment_apps, true);
navController.navigate(R.id.fragment_company);
the first line removes the back Stack till it reaches the fragment specified in my case it's the home fragment so it's removes all the back stack completely , and when the user clicks back in the fragment_company he closes the app.
Going to add another answer here as none of the above worked for me ... we have multiple nav graphs.
findNavController().navigate(R.id.dashboard_graph,null,NavOptions.Builder().setPopUpTo(findNavController().graph.startDestination, true).build())
This was the only way that I could successfully clear the full back stack. Google really need to make this simpler.
NOTE: Clear task is deprecated, official description is
This method is deprecated. Use setPopUpTo(int, boolean) with the id of the NavController's graph and set inclusive to true.
Old Answer
If you don't wanna go through all that fuzz in code, you can simply check Clear Task in Launch Options in properties of the action.
Edit: As of Android Studio 3.2 Beta 5, Clear Task is no longer visible in Launch Options window, but you can still use it in navigation's XML code, in action tag, by adding
app:clearTask="true"
NavController navController
=Navigation.findNavController(requireActivity(),
R.id.nav_host_fragment);// initialize navcontroller
if (navController.getCurrentDestination().getId() ==
R.id.my_current_frag) //for avoid crash
{
NavDirections action =
DailyInfoSelectorFragmentDirections.actionGoToDestionationFragment();
//for clear current fragment from stack
NavOptions options = new
NavOptions.Builder().setPopUpTo(R.id.my_current_frag, true).build();
navController.navigate(action, options);
}
I finally figure it out thanks to How to disable UP in Navigation for some fragment with the new Navigation Architecture Component?
I had to specify .setClearTask(true) as a NavOption.
mAuth.signInWithCredential(credential)
.addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Log.d(TAG, "signInWithCredential:success");
NavOptions.Builder navBuilder = new NavOptions.Builder();
NavOptions navOptions = navBuilder.setClearTask(true).build();
NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment,null,navOptions);
} else {
Log.w(TAG, "signInWithCredential:failure", task.getException());
}
}
});
use this code
navController.navigateUp();
then call new Fragment
android version 4.1.2
Here is how I am getting it done.
//here the R.id refer to the fragment one wants to pop back once pressed back from the newly navigated fragment
val navOption = NavOptions.Builder().setPopUpTo(R.id.startScorecardFragment, false).build()
//now how to navigate to new fragment
Navigation.findNavController(this, R.id.my_nav_host_fragment)
.navigate(R.id.instoredBestPractice, null, navOption)
For
// Navigation library
def nav_version = "2.3.5"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
This solution work for me
findNavController().popBackStack(R.id.<Current Fragment Id In NavGraph>, true)
findNavController().navigate(R.id.< Your Destination Fragment in NavGraph>)
In my case where I used Navigation component with NavigationView (menu drawer):
1.
mNavController.popBackStack(R.id.ID_OF_FRAGMENT_ROOT_TO_POP, true)
mNavController.navigate(
R.id.DESTINATION_ID,
null,
NavOptions.Builder()
.setPopUpTo(R.id.POP_TO_DESTINATION_ID, true)
.build()
)
I wanted to clear the stack after clicking on logout on side menu drawer!
Hope that helped someone!
You can override the back pressed of the base activity like this :
override fun onBackPressed() {
val navigationController = nav_host_fragment.findNavController()
if (navigationController.currentDestination?.id == R.id.firstFragment) {
finish()
} else if (navigationController.currentDestination?.id == R.id.secondFragment) {
// do nothing
} else {
super.onBackPressed()
}
}
Non of the solutions above works for me.
After spending hours on it, here is my solution:
Note: I have multiple nav_graphs and switching between fragments in different nav_graphs.
Define your action as below in xml:
<action
android:id="#id/your_action_id"
app:destination="#id/the_fragment_id_you_want_to_navigate_to"
app:popUpTo="#id/nav_graph_which_contains_destination_fragment"
app:popUpToInclusive="true" />
Navigate using action above from your Java/Kotlin code:
findNavController(R.id.your_nav_name)?.apply {
navigate(R.id.your_action_id)
backQueue.clear()
}
For Jetpack Compose ❤️
navHostController.navigate(Routes.HOME) {
this.popUpTo(Routes.ONBOARDING) {
this.inclusive = true
}
}
I struggled for a while to prevent the back button from going back to my start fragment, which in my case was an intro message that should only appear once.
The easy solution was to create a global action pointing to the destination that the user should stay on. You have to set app:popUpTo="..." correctly - set it to the destination you want to get popped off. In my case it was my intro message. Also set app:popUpToInclusive="true"
In my case, I am using 2 different activities that have their own respective navigation graphs. My first activity is the host for "nav_graph" and has fragments that deal with authentification and the second is the host for "nav_graph_home". Here you can see the settings I have done for nav_graph.
nav_graph example
Then back in my code for the login fragment, I have this written :
findNavController().navigate(R.id.action_logInFragment_to_nav_graph_home)
After the user logs in and they hit the back button the app will close. Remember to set the pop behavior so it pops till your current navigation graph that contains your login fragment without including it.
Edit:
After this, the up button still appears in the top bar. To avoid this behavior we need to tell the first activity which fragments are considered top level. To do this simply add in the params list of the "setupActionBarWithNavController()" method in addition to the nav host fragment an App bar configuration that contains a set of the home fragment of your first navigation graph and your second. Your code should look something like this:
class MainActivity : AppCompatActivity(R.layout.activity_main) {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Retrieve NavController from the NavHostFragment
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
// Set up the action bar for use with the NavController
setupActionBarWithNavController(navController, AppBarConfiguration(setOf(R.id.logInFragment,R.id.homeFragment)))
}
/**
* Handle navigation when the user chooses Up from the action bar.
*/
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
This is my first ever contribution, hope this helps.
For androidx.compose version 1.2.0+
I had a few issues with lower versions but 1.2 plus (beta at the time of writing this), works perfectly.
Better syntax for the navGraph in Compose:
navController.navigate(item.name) {
navController.graph.startDestinationRoute?.let { route ->
// use saveState = false to NOT save the state for the popped route
popUpTo(route) { saveState = true }
}
launchSingleTop = true
restoreState = true
}
I am using button to navigate to other fragments so on each button click I am doing this.
val navOptions = NavOptions.Builder().setLaunchSingleTop(true).setPopUpTo(R.id.homeFragment, false).build()
findNavController(R.id.mainNavHostFragment).navigate(R.id.destination, null, navOptions)
You can do as simple as:
getFragmentManager().popBackStack();
If you want to check the count you can do as:
getFragmentManager().getBackStackEntryCount()

showandwait changing variable value

I'm having a problem with a variable I'm using to track the status of a user activity. In a GUI I have a button that, on clicking the button launches a second GUI. In that GUI, the user can either complete the activity started in the first GUI or not.
If the user cancels the second GUI, then the idea is to go back to the first GUI, leaving all variables and lists with their current values. If the second GUI completes the activity of the first GUI, then all variables and lists should be reset.
To track this, I have a variable (Boolean complete) initially set to FALSE. In the second GUI, when the "OK" button is clicked (rather than the "Cancel" button), the second GUI calls a method in the first GUI, changing the value of "complete" to TRUE.
To see what the heck is going on, I have System.out.println at several points allowing me to see the value of "complete" along the way. What I see is this:
Launching first GUI - complete = FALSE
Launching second GUI - complete = FALSE
Clicking "OK" in second GUI - complete = TRUE
Second GUI closes itself, returning to complete first GUI activity
First GUI finishes activity with complete = FALSE
I'm assuming it is because I am launching the second GUI with a showandwait, and when the method containing the showandwait begins, the value of "complete" = FALSE. The value changes in the WAIT part of show and wait, then the method continues and that is where I get the value still being FALSE, though it was changed to TRUE.
Here is a summary of the code in question (if you need exact code, it's longer, but I can post on request):
completeButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
try {
System.out.println("b4 calc = " + complete); // complete = FALSE
// all the code to create the calcStage
calcStage.showAndWait(); // second GUI, which calls a method in THIS
// class that changes complete to TRUE. That method
// (in THIS file) also has a println that shows the change.
getComplete(); // tried adding this method to check the value of
// "complete" after the change made by the calcStage
// (which calls a method in this same file)
System.out.println("Complete? " + complete);
// this shows complete = FALSE,
// though in the calcStage it was changed to TRUE
if (salecomplete) {
// code that should reset all variables and lists if the activity was completed
}
}
}
}
The question here is why does the second GUI successfully change the value of "complete", but when I return to the first GUI it still sees complete as FALSE? And how can I get around this?
Try having the controller of the second GUI calling a method in the first GUI's controller to modify that complete variable
For example:
// Code to handle the OK button being pressed
#Override
public void handle(ActionEvent t) {
// Do validation and work
//reference to the first controller object
firstController.setComplete(true);
}

MessageBox.Show in App Closing/Deactivated events

I have a MessageBox being shown in Application Closing/Deactivated methods in Windows Phone 7/8 application. It is used to warn the user for active timer being disabled because app is closing. The App Closing/Deactivated events are perfect for this, because putting logic in all application pages would be a killer - too many pages and paths for navigation. This works just fine - message box displays OK in WP7.
I also know for breaking changes in the API of WP8. There it is clearly stated that MessageBox.Show in Activated and Launching will cause exception.
The problem is that in WP8 the message box does not get shown on app closing. Code is executed without exception, but no message appears.
P.S. I've asked this on MS WP Dev forum but obviously no one knew.
Move the msgBox code from the app closing events and into your main page codebehind. Override the on back key press event and place your code there. This is how it was done on 7.x:
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
if (MessageBox.Show("Do you want to exit XXXXX?", "Application Closing", MessageBoxButton.OKCancel) == MessageBoxResult.Cancel)
{
// Cancel default navigation
e.Cancel = true;
}
}
FYI - On WP8 it looks like you have to dispatch the MsgBox Show to a new thread.
This prompts the user before the app ever actually starts to close in the event model. If the user accepts the back key press is allowed to happen, otherwise its canceled. You are not allowed to override the home button press, it must always go immediately to the home screen. You should look into background agents to persist your timer code through suspend / resume.
Register BackKeyPress event on RootFrame.
RootFrame.BackKeyPress += BackKeyPressed;
private void BackKeyPressed(object sender, CancelEventArgs e)
{
var result = (MessageBox.Show("Do you want to exit XXXXX?", "Application Closing", MessageBoxButton.OKCancel));
if (result == MessageBoxResult.Cancel)
{
// Cancel default navigation
e.Cancel = true;
}
}

Eclipse Plug-in:Focus lost after activating a view via (controlSite.doVerb(OLE.OLEIVERB_INPLACEACTIVATE));

I have a view which is initialised via OleControlSite and invoked via OleAutomation. It is actually a media player which I call in a view after user right clicks the file and calls Play in the context menu. Whenever I play a file by first right clicking and calling Play, it plays absolutely fine. The problem is when user displays the view before doing a right click(Window->Show View->Other->MyView) and then tries to do a right click and Play, at this point of time ISelection returns null and hence nothing plays.
IWorkbenchPage iwPage=PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
ISelection selection=iwPage.getSelection();
The problem is the selections somehow loses focus if the view has been invoked in the fashion described.
This is how the view is initialised when the plugin is loaded
public void createPartControl(Composite parent) {
frame = new OleFrame(parent, SWT.APPLICATION_MODAL);
// OleControlSite controlSite;
try {
controlSite = new OleControlSite(frame, SWT.APPLICATION_MODAL,"WMPlayer.OCX.7");
controlSite.doVerb(OLE.OLEIVERB_INPLACEACTIVATE);
oleAutomation = new OleAutomation(controlSite);
makeActions();
fillActionBars();
} catch (SWTException ex) {
MessageBox box = new MessageBox(getSite().getShell(),SWT.ICON_INFORMATION);
box.setMessage("Failed to Initialise Media Player.");
box.setText("Error");
box.open();
ex.printStackTrace();
return;
}
}
Is there a way where we can force the focus to the Project Explorer where the current file is selected ?