I have a bottom sheet dialog fragment that i use as a menu for my bottom app bar.
If i click on the menu icon really quick two times, the dialog shows up two times and i have to close it two times which is annoying.
My code is as follows:
ActivityHome.kt
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
if(mBottomNavDrawerFragment != null && mBottomNavDrawerFragment!!.dialog!!.isShowing){
mBottomNavDrawerFragment?.dismiss()
return false
}
mBottomNavDrawerFragment = RoundedBottomSheetDialogFragment()
mBottomNavDrawerFragment?.show(supportFragmentManager, mBottomNavDrawerFragment?.tag)
true
}
R.id.BottomAppBar_fromHomeActivity_MenuMain_Search -> {
Toast.makeText(this, "Not Implemented yet!", Toast.LENGTH_SHORT).show()
false
}
else -> true
}
}
Can anyone help? Thanks
Show the dialogFragment using a tag. Check if the tag exists in the stack before showing it again
if(getChildFragmentManager().findFragmentByTag(FragmentDialog.TAG) == null) {
fragmentDialog.show(getChildFragmentManager(), FragmentDialog.TAG);
}
You can do a little bit hack here. Here is the code.
private var saveClickCounter = 0
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return when (item?.itemId) {
R.id.home -> {
if (saveClickCounter++ == 0) {
//Your Dialog Showing Code
Handler().postDelayed({
saveClickCounter=0
},1000)
}
true
}
R.id.BottomAppBar_fromHomeActivity_MenuMain_Search -> {
Toast.makeText(this, "Not Implemented yet!", Toast.LENGTH_SHORT).show()
false
}
else -> true
}
}
Make a variable saveClickCounter to store your counts.
On clicks increase the value of saveClickCounter and change it to zero after N seconds of delayed. I used 1 second in below code.
Related
i am very beginner. sorry if its too easy or im stupid. i want to make bottom navigation menu with material design 3 kotlin. but this error comes. i want to change visibility of scroll view. what is problem?
again, im using this: m3.material.io
NavigationBarView.OnItemSelectedListener { item ->
when(item.itemId) {
R.id.main -> {
likes.visibility = View.GONE
mainmenu.visibility = View.VISIBLE
}
R.id.starred -> {
mainmenu.visibility = View.GONE
likes.visibility = View.VISIBLE
}
else -> false
}
}
Going off memory, but I think you missed the word set, as in setOnItemSelectedListener, and the listener needs to return a Boolean for all cases. You only returned it for the else case. Very unlikely you need to worry about returning false (for fall-through behavior), so I'd just return treu after the when statement.
NavigationBarView.setOnItemSelectedListener { item ->
when(item.itemId) {
R.id.main -> {
likes.visibility = View.GONE
mainmenu.visibility = View.VISIBLE
}
R.id.starred -> {
mainmenu.visibility = View.GONE
likes.visibility = View.VISIBLE
}
else -> { }
}
true
}
If you're possibly going to be adding more tabs, I suggest doing your logic like this so it's easier to maintain all the possible cases without having to repeat similar lines of code. The way you're doing it now, you have to write a line of code per tab for each view, and make sure you get the visibility right for each case. You can use the isVisible extension property to hide/show views with Boolean logic.
NavigationBarView.setOnItemSelectedListener { item ->
mainmenu.isVisible = item == R.id.main
likes.isVisible = item == R.id.starred
true
}
I think instead of the onItemSelected, It should be setNavigationItemSelectedListener. I had the same issue with the material top nav bar so, maybe try this out too. Also, try adding log statements and verify if the click statements are working. here's a sample code for reference -
binding.navigationViewMainScreen.setNavigationItemSelectedListener {
Log.d(TAG, "navigationViewMainScreen working")
when (it.itemId) {
R.id.log_in -> {
loginPrompt()
true
}
R.id.log_out -> {
Toast.makeText(requireContext(), "Logged Out", Toast.LENGTH_SHORT)
.show()
CoroutineScope(Dispatchers.IO).launch {
accountDataStore.logOut(requireContext())
}
true
}
R.id.sign_up -> {
findNavController()
.navigate(
MainScreenFragmentDirections.actionMainScreenFragmentToSignUpFragment()
)
true
}
R.id.remove_account -> {
Toast.makeText(requireContext(), "Account Removed", Toast.LENGTH_SHORT)
.show()
CoroutineScope(Dispatchers.IO).launch {
accountDataStore.resetAccounts(requireContext())
}
true
}
R.id.switch_account -> {
switchAccountPrompt()
true
}
R.id.contract_interface -> {
if (userLoggedIn) {
findNavController()
.navigate(
MainScreenFragmentDirections
.actionMainScreenFragmentToContractScreenFragment()
)
} else {
Toast.makeText(
requireContext(), "Log In Necessary To Continue", Toast.LENGTH_SHORT
).show()
loginPrompt()
}
true
}
R.id.about -> {
findNavController()
.navigate(
MainScreenFragmentDirections
.actionMainScreenFragmentToAboutFragment()
)
Log.d(TAG, "about working")
true
}
R.id.exit -> {
Log.d(TAG, "exit working")
logOutAndExit()
true
}
else -> {
throw IllegalArgumentException("side menu item not registered.")
}
}
}
just focus on the content inside. like this one -
R.id.log_in -> {
loginPrompt()
true
}
here we can see an action is performed and it returns true to verify that. Just don't forget the true at the ends. that is used to verify an action has been performed after the button was clicked. Hope it helps.
This is the code in onNavigationItemSelected
R.id.nav_subscribed -> {
if (prefs.getLong("userid", 0L) == 0L) {
when (currentGal) {
"top" -> {
// navView.setCheckedItem(R.id.nav_top)
// navView.menu.getItem(R.id.nav_top).isChecked = true
}
}
val i = Intent(this, Login::class.java)
this.startActivity(i)
} else {
if(currentGal != "subscriptions"){
getMemes("subscriptions", 0)
}
}
}
I try to prevent nav_subscribed to be checked when the user isn't logged in and check/keep checked nav_top
navView.setCheckedItem(R.id.nav_top)
isn't working and
navView.menu.getItem(R.id.nav_top).isChecked = true
crashed the app. So how to do this? Btw android is awful broken garbage software
navigationView?.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.nav_subscribed -> {
// if you don't want an item to be selected return false
return#setOnNavigationItemSelectedListener false
}
R.id.nav_top -> {
}
}
//return true to select the other items
true
}
#ExperimentalCoroutinesApi
class WeekdayTimesFragment #Inject constructor(viewModelFactory: SavedStateViewModelFactory.Factory)
:Fragment(R.layout.fragment_weekday_times), WeekdayAlarmTimesAdapter.OnWeekdayAlarmTimePressed, ActionModeListener {
private lateinit var weekdayAlarmTimesAdapter: WeekdayAlarmTimesAdapter
private val weekdayTimesViewModel
by viewModels<WeekdayTimesViewModel> { viewModelFactory.create(this) }
override val actionCallback = PrimaryActionCallback(this)
private var weekday: Int = 0
private val isActionModeActive: Boolean
get() = weekdayTimesViewModel.isActionModeActive
private val numSelected: Int
get() = weekdayTimesViewModel.selectedTimePositions.size
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
weekday = requireArguments().get("WEEKDAY") as Int
if(isActionModeActive) { //action mode may be active prior to rotation
startActionMode() //for some reason, starting action mode here does not work....
}
initRecyclerView() //will determine what recyclerview items are selected via the viewmodel
weekdayTimesViewModel.getTimesAndAlarmsForSelectedWeekday(Weekday[weekday])
.observe(viewLifecycleOwner) { times->
weekdayAlarmTimesAdapter.submitWeekdayTimes(times)
}
}
override fun alarmTimePressed(wasSelected: Boolean, position: Int, time: WeekdayTime) {
if(wasSelected) { //this item was selected prior to being selected, effectively unselecting it
weekdayTimesViewModel.selectedTimePositions.remove(position)
} else {
weekdayTimesViewModel.selectedTimePositions[position] =
Time(time.hour, time.minute, Weekday[weekday], time.alarm.alarmId, time.isDisabled, time.id)
}
if(numSelected == 0 && isActionModeActive) { //no more times are selected, turn off action mode
destroyActionMode() //need to de-select recyclerview items
} else {
if (!isActionModeActive) {
startActionMode()
} else {
actionCallback.changeTitle("$numSelected selected")
}
}
}
private fun startActionMode() {
actionCallback
.startActionMode(
requireView(),
R.menu.toolbar_contextual_menu,
"$numSelected selected")
//fragment hosting the viewpager
(requireParentFragment() as ActionModeListenerController).currentActionModeListener = this
weekdayTimesViewModel.isActionModeActive = true
}
override fun onActionItemClick(item: MenuItem?) {
item?.let {
when(it.itemId){
R.id.toolbar_disable_alarm-> {
requireContext().createGenericAlertDialog(
message = if(numSelected == 1) "Are you sure you want to disable this alarm?"
else "Are you sure you want to disable these alarms?",
positiveListener = { _, _ ->
weekdayTimesViewModel.disableSelectedTimes()
showUndoSnackbar(true)
}
).show()
}
R.id.toolbar_delete_alarms-> {
requireContext().createGenericAlertDialog(
message = if(numSelected == 1) "Are you sure you want to delete this alarm?"
else "Are you sure you want to delete these alarms?",
positiveListener = { _, _ ->
weekdayTimesViewModel.deleteSelectedTimes()
showUndoSnackbar(false)
}
).show()
}
}
}
}
//when we aren't destroying the view but need to end action mode (changing tab or after we confirm an action)
//we may want to do specific things specific to certain listeners, e.g. de-select certain recyclerview items.
override fun destroyActionMode() {
actionCallback.finishActionMode()
weekdayTimesViewModel.isActionModeActive = false
//also un-select all of the elements that have been selected
weekdayTimesViewModel.selectedTimePositions.clear()
//redraw recyclerview as well
rv_weekday_times.resetAdapterState()
}
private fun showUndoSnackbar(disablingTimes: Boolean){
val message =
if(disablingTimes) "Your alarm times have been disabled." else "Your alarm times have been deleted."
Snackbar.make(requireView(), message, Snackbar.LENGTH_LONG).setAction("UNDO"){
if(disablingTimes)
weekdayTimesViewModel.restoreDisabledTimes()
else
weekdayTimesViewModel.restoreDeletedTimes()
}.setActionTextColor(ContextCompat.getColor(requireContext(), R.color.colorPrimary))
.setAnchorView(requireActivity().bottom_nav_view).show()
}
//when rotating screen or navigating we can just end action mode. The state of whether we are
//in action mode either doesn't matter (navigation) or is already persisted, along with the selected
//items in the viewmodel(rotation)
override fun onDestroyView() {
actionCallback.finishActionMode()
super.onDestroyView()
}
private fun initRecyclerView() {
weekdayAlarmTimesAdapter = WeekdayAlarmTimesAdapter(this, weekdayTimesViewModel.selectedTimePositions.keys)
rv_weekday_times.apply {
adapter = weekdayAlarmTimesAdapter
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
}
weekdayAlarmTimesAdapter.selectedItems.clear()
//clears selected items as these items no longer remain consistently for the lifecycle of the view
}
}
So my ActionMode works great when I get a callback (alarmTimePressed) which is through a click listener set in the recyclerview's OnBindViewHolder. I start my ActionMode and it works great, I save whether ActionMode is active in my viewmodel, which works well upon rotation, as if it is active prior to rotation, it still remains active. As you can see in my OnViewCreated, I call startActionMode() when ActionMode is meant to be active. The problem is that when I start it from there, the onCreateActionMode callback is never called. This is strange because even after rotating, if I select an item in the RecyclerView it correctly launches ActionMode. The view I am passing to startActionMode is the same in both cases. If you need the code for PrimaryActionCallback, it's here: https://hastebin.com/vepujuhefe.m. Didn't think it was very necessary but in case you felt like it was necessary to see :). Anyone know what's going on here?
I have this interactive game with 2 buttons and 1 image where images are stored in a map of drawables. The current image is var currentImageDrawable, so when you click a button, the image changes together with the currentImageVariable. But since I added a TextView, just to change the text when a specific image is displayed, unfortunately it doesn't change. It just remains at the first text which is "Start the game?".
import com.example.thegame.R.drawable.*
var tv = findViewById<TextView>(R.id.textView)
var currentImageDrawable = menu
data class Choice(val choice1: Int, val choice2: Int)
val choicesMap = mapOf(
menu to Choice(door1, menu),
door1 to Choice(door_inside, door_back),
door_inside to Choice(door_inside2, door_inside2_1),
door_back to Choice(door_back2, door_back2_1)
)
buttonYes.setOnClickListener {
currentImageDrawable = choicesMap[currentImageDrawable]!!.choice1
imageView.setImageResource(currentImageDrawable)
}
buttonNo.setOnClickListener {
currentImageDrawable = choicesMap[currentImageDrawable]!!.choice2
imageView.setImageResource(currentImageDrawable)
if (currentImageDrawable== menu) {
finishAffinity()
}
}
if (currentImageDrawable== menu) {
tv.setText("Start the game?") }
else if (currentImageDrawable== door1) {
tv.setText("Open the door?") }
else {
tv.setText("")
}
/*
when (currentImageDrawable) {
menu -> tv.setText("start the game?")
door1 -> tv.setText("open the door?")
else -> tv.text = ("")
}
*/
I think your code comes from your OnCreate method, can it be?
OnCreate is executed once when you create your activity. To change the text content when clicking the button you have to write the part in the OnClickListener.
buttonYes.setOnClickListener {
currentImageDrawable = choicesMap[currentImageDrawable]!!.choice1
imageView.setImageResource(currentImageDrawable)
if (currentImageDrawable== menu) {
tv.setText("Start the game?") }
else if (currentImageDrawable== door1) {
tv.setText("Open the door?") }
else {
tv.setText("")
}
}
buttonNo.setOnClickListener {
currentImageDrawable = choicesMap[currentImageDrawable]!!.choice2
imageView.setImageResource(currentImageDrawable)
if (currentImageDrawable== menu) {
tv.setText("Start the game?")
finishAffinity()
} else if (currentImageDrawable== door1) {
tv.setText("Open the door?")
} else {
tv.setText("")
}
}
The OnClick method is not executed immediately, but each time the button is clicked.
I am trying to make a popup menu and have things happen depending on which item is clicked on. The on click listener for the menu items expects a return type of type Boolean. I have given it a return type but it still all shows up in red with a message of "Expected a value of type Boolean". Could someone tell me what I have wrong here? ( I am aware I haven't made the menu clicks do anything)
val menuButton = findViewById<Button>(R.id.categoryImageButton)
menuButton.setOnClickListener(View.OnClickListener {
fun onClick(view: View){
val popup = PopupMenu(this,menuButton)
popup.menuInflater.inflate(R.menu.popup_menu, popup.menu)
popup.setOnMenuItemClickListener(PopupMenu.OnMenuItemClickListener {
**fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.techItem -> {
return true
}
R.id.clothItem -> {
return true
}
else -> return false
}
}**
})
}
})
What you have right now is somewhere half way between object expressions and a SAM constructor. Here are some options for fixing it.
You can use the full object expression syntax, which looks like this:
popup.setOnMenuItemClickListener(object: PopupMenu.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.techItem -> {
return true
}
R.id.clothItem -> {
return true
}
else -> return false
}
}
})
You can improve the above slightly by using when as an expression, and returning it:
popup.setOnMenuItemClickListener(object: PopupMenu.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.techItem -> {
true
}
R.id.clothItem -> {
true
}
else -> false
}
}
})
Or you can just use SAM conversion to define the single function that you'd have to implement in a lambda:
popup.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.techItem -> {
true
}
R.id.clothItem -> {
true
}
else -> false
}
}