I have a simple android application, In my application, there is one image icon, and when I click the icon, I want to show context menu. My code is debugging but when I click the image button, nothing change, so context menu not work, I do not know where is the problem, Any idea will be appreciated.
MenuFragment:
class MenuFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_menu, container, false)
popupMenu()
}
private fun popupMenu() {
val popupMenu = PopupMenu(requireContext(), menu)
popupMenu.inflate(R.menu.menu)
popupMenu.setOnMenuItemClickListener {
when(it.itemId) {
R.id.menu_one -> {
Toast.makeText(requireContext(), "menu1", Toast.LENGTH_SHORT).show()
true
}
R.id.menu_two -> {
Toast.makeText(requireContext(), "menu2", Toast.LENGTH_SHORT).show()
true
}
else -> true
}
}
menu.setOnLongClickListener {
try {
val popup = PopupMenu::class.java.getDeclaredField("mPopup")
popup.isAccessible = true
val menu = popup.get(popupMenu)
menu.javaClass
.getDeclaredMethod("setForceShowIcon",Boolean::class.java)
.invoke(menu, true)
}catch(e: Exception) {
e.printStackTrace()
}finally {
popupMenu.show()
}
true
}
}
}
fragment_menu:
<ImageView
android:id="#+id/menu"
android:layout_width="wrap_content"
android:layout_height="16dp"
android:src="#drawable/ic_menu"
android:text="5:33 PM"
android:textSize="12sp"
/>
menu:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/menu_one"
android:title="Menu1"
android:icon="#drawable/ic_menu_one"
/>
<item
android:id="#+id/menu_two"
android:title="Menu2"
android:icon="#drawable/ic_menu_two"
/>
It seems you want to show the menu as soon as possible, since the function invocation is after the return statement then it never happen.
You have to leverage the fragment lifecycle. Override onViewCreated and call it there
override... onViewCreated(...) {
super....
popUpMenu()
}
The method onViewCreated happens immediately after the view is created. If you need it when the view is ready to interact onStart. In both cases, make sure backward navigation doesn't show it again by mistake.
Related
I'm currently trying to enable one menu item I have on my Options Menu and disabling another and vice versa when it's pressed. I don't quite know how this is done and every other attempt I've tried hasn't been successful.
Here's how my app is currently set up. I'm using a single activity and multiple fragments. My MainFragment has the following within it to set up the Options menu:
class MainFragment: Fragment() {
...
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMainBinding.inflate(inflater, container, false)
...
setHasOptionsMenu(true)
(activity as AppCompatActivity).setSupportActionBar(binding.mfToolbar)
...
return binding.root
}
...
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
super.onOptionsItemSelected(item)
when(item.itemId) {
R.id.recycler_view_toggle_list -> {
// TODO: Implement what happens here with the RecyclerView
// Also disable this menu item and enable grid view item
}
R.id,recyclver_view_toggle_grid -> {
// TODO: Implement what happens here with the RecyclerView
// Also disable this menu item and enable list view item
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
menu_item.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.jre.projectcounter.ui.main.MainActivity">
<item
android:id="#+id/recycler_view_toggle"
android:icon="#drawable/ic_view_list"
android:title="#string/list_view"
android:visible="true"
app:showAsAction="ifRoom"/>
<item
android:id="#+id/recycler_view_toggle_grid"
android:icon="#drawable/ic_view_grid"
android:title="#string/grid_view"
android:visible="false"
app:showAsAction="ifRoom" />
<item
android:id="#+id/action_settings"
android:orderInCategory="100"
android:title="Settings"
app:showAsAction="never" />
</menu>
When I try to do the following within the when statement under R.id.recycler_view_toggle_list:
binding.mfToolbar.menu[R.id.recycler_view_toggle_list].isVisible = false
binding.mfToolbar.menu[R.id.recycler_view_toggle_grid].isVisible = true
I get the following exception:
java.lang.IndexOutOfBoundsException
What am I doing wrong or missing to get the options to switch between each other?
You need to use findItem if you're using the id of item. Because when you do menu[0] it expects index & not id.
So in your case it should be something like this:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
super.onOptionsItemSelected(item)
when(item.itemId) {
R.id.recycler_view_toggle -> {
binding.mfToolbar.menu.findItem(R.id.recycler_view_toggle).isVisible = false
binding.mfToolbar.menu.findItem(R.id.recycler_view_toggle_grid).isVisible = true
}
}
return true
}
My application has one Activity and several fragments. Navigation Component was used for navigation and it is not very complicated. Also BottomNavigationComponent was used for bottom navigation.
There are three top level fragments which can be accessed via bottom navigation and there is an options menu at one of the top level fragments. Also this is not a complicated menu,too; there is just one item.
So, this is not a big deal and created menu like below.
notifications_menu
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/dismiss_all"
android:title="#string/dismiss_all"
android:orderInCategory="10"
app:showAsAction="ifRoom" />
</menu>
NotificationsFragment.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel =
ViewModelProvider.NewInstanceFactory().create(NotificationsViewModel::class.java)
setHasOptionsMenu(true)
(activity as MainActivity).supportActionBar?.setDisplayHomeAsUpEnabled(false)
(activity as MainActivity).supportActionBar?.setDisplayShowHomeEnabled(false)
//other stuff
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.notifications_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.dismiss_all) {
// there is no code here yet
return true
}
return super.onOptionsItemSelected(item);
}
And here is the result...
The problem is, as you can see above, when I tap Dismiss All button it navigates to the initial fragment. But I couldn't understand why?
After digging two hours I found that MainActivity's onOptionsItemSelected is causing this situation. Because as you can see it is handling all menu items and call onBackPressed event.
override fun onOptionsItemSelected(menuItem: MenuItem?): Boolean {
if (menuItem != null) {
onBackPressedDispatcher.onBackPressed()
}
return super.onOptionsItemSelected(menuItem)
}
Handling correctly this method solved my problem.
Here is the correct version of the code above.
override fun onOptionsItemSelected(menuItem: MenuItem?): Boolean {
val id = menuItem?.itemId
if (id == android.R.id.home) {
onBackPressedDispatcher.onBackPressed()
return true
}
return super.onOptionsItemSelected(menuItem)
}
I have a fragment that is called from the activity_main screen when a main menu drop down option is clicked. I am trying to have the fragment close and show the activity_main screen again when the cancel button is clicked. I have user input in the activity_main screen that I would like to still be there if the fragment is closed while clicking cancel but at this point i will settle for the fragment just closing and the activity_main showing back at it's original state.
I am using androidx and all of the answers I have found so far have been compatible with android.support.v7
This is the code that i have in my fragment, it does not do anything at all when I click the 'Cancel' button, the fragment just continues to sit there over the activity_main.
class SaveFragment: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.savefragment, container, false)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
b_cancel.setOnClickListener {
fragmentManager?.popBackStack()
}
}
}
Here is the code that calls the savefragment from the MainActivity:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.i_save -> {
val SaveFragment = SaveFragment()
supportFragmentManager.beginTransaction().replace(R.id.activity_main_main, SaveFragment).commit()
true
}
R.id.i_recall -> {
val RecallFragment = RecallFragment()
supportFragmentManager.beginTransaction().replace(R.id.activity_main_main, RecallFragment).commit()
true
}
else -> super.onOptionsItemSelected(item)
}
}
Any help at all in Kotlin would be appreciated.
I am reading this from Google about using DialogFragment. Everything works until the section Creating a Custom Layout: when the call is done to display a DialogFragment with a custom layout I get a OutOfMemoryError exception.
I have just slighty modified the code from the article, and my app only contains the 3 elements below:
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
showDialog()
}
}
fun showDialog() {
// Create an instance of the dialog fragment and show it
val dialog = FireMissilesDialogFragment()
dialog.show(supportFragmentManager, "NoticeDialogFragment")
}
}
FireMissilesDialogFragment.kt
class FireMissilesDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
builder.setView(layoutInflater.inflate(R.layout.dialog_layout, null))
// Add action buttons
.setPositiveButton(R.string.ok) { dialog, id ->
Log.d("FireMissiles", "TEST")
}
.setNegativeButton(R.string.cancel) { dialog, id ->
getDialog().cancel()
}
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
dialog_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<EditText
android:id="#+id/username"
android:inputType="textEmailAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="4dp"
android:hint="#string/ok" android:importantForAutofill="no"/>
<EditText
android:id="#+id/password"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="16dp"
android:fontFamily="sans-serif"
android:hint="#string/cancel" android:importantForAutofill="no"/>
</LinearLayout>
My code has only these 3 elements above. I guess there is somewhere a memory leak but I cannot find it.
Does someone have an idea?
Answers to comments and solution:
#Sam I do not use any image, this project is just a attempt to use DialogFragment,
hence it is based on standard empty Activity project with fab, only thing I did not show is the activity_main.xml but it is the standard one.
#Tobias, thanks for the hint but it did not solve the problem :(
#user8159708, thank you!! that solved the problem. My code is now (and it is the only thing I changed):
class FireMissilesDialogFragment : DialogFragment() {
lateinit var mView: View
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
mView = inflater.inflate(R.layout.dialog_layout, container, false)
setDialog()
return mView
}
fun setDialog(){
activity?.let {
val builder = AlertDialog.Builder(it)
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
builder.setView(mView)
// Add action buttons
.setPositiveButton(R.string.ok) { dialog, id ->
Log.d("FireMissiles", "TEST")
}
.setNegativeButton(R.string.cancel) { dialog, id ->
Log.d("FireMissiles", "TEST")
//getDialog().cancel()
}
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
Don't create your dialog using an alert builder.
Remove your override of onCreateDialog.
Inflate your view in onCreateView:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.dialog_layout, container, false)
}
Add some buttons to your layout yourself, and set your onClickListeners manually after inflating your view.
This sounds like loop.
Try to remove getDialog().cancel() and just return null instead.
You don't need to explicitely close Dialoges.
I think you are doing it wrong. You don't need to use AlertDialog.Builder as you are already extending from DialogFragment.
You can follow this link.
It is in Java, but in Kotlin it will be similar.
class BottomNavigationDrawerFragment: BottomSheetDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_bottomsheet, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
navigation_view.setNavigationItemSelectedListener { menuItem ->
// Bottom Navigation Drawer menu item clicks
when (menuItem!!.itemId) {
R.id.nav1 -> context!!.toast("you clicked one")
R.id.nav2 -> context!!.toast("you clicked two")
R.id.nav3 -> context!!.toast("you clicked three")
}
true
}
}
// This is an extension method for easy Toast call
fun Context.toast(message: CharSequence) {
val toast = Toast.makeText(this, message, Toast.LENGTH_SHORT)
toast.setGravity(Gravity.BOTTOM, 0, 600)
toast.show()
}
}
What i want to achieve is after clicking navigation icon in bottom app bar a modal bottom sheet is created and and a navigation drawer is shown in it.In above code i kept three items in it.Up to here everything is okay but when comes to handling item clicking part then the line:
navigation_view.setNavigationItemSelectedListener { menuItem ->
shows error. It tells :
unresolved type:setNavigationItemSelectedListener
And unresolved type in:menuItem
Here is fragment_bottomsheet.xml :
<android.support.design.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.navigation.NavigationView
android:id="#+id/navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="#menu/bottom_nav_drawer_menu"/>
</android.support.design.widget.ConstraintLayout>
What is wrong in am doing here?
It seems like issue comes from Context.toast(message: CharSequence) which you could use anko for that:
implementation "org.jetbrains.anko:anko:0.10.6"
Anyways, you can check the output by:
Toast.makeText(context, "Clicked", Toast.LENGTH_LONG).show()
And you'll see that it will work.
UPDATE:
Try using this inside your BottomNavigationDrawerFragment:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
navigation_view.setNavigationItemSelectedListener { menuItem ->
// Bottom Navigation Drawer menu item clicks
when (menuItem.itemId) {
R.id.home ->{
Toast.makeText(context, "Clicked", Toast.LENGTH_LONG).show()
}
// R.id.nav1 -> context!!.toast(getString(R.string.nav1_clicked))
}
// Add code here to update the UI based on the item selected
// For example, swap UI fragments here
true
}
close_imageview.setOnClickListener {
this.dismiss()
}
disableNavigationViewScrollbars(navigation_view)
}
The below method is worked for me.
class BottomNavigationDrawerFragment : BottomSheetDialogFragment() {
private val TAG: String = ImageChooserDialog::class.simpleName.toString()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.bottom_sheet_image_chooser, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<NavigationView>(R.id.navigation_view)
.setNavigationItemSelectedListener {
when(it.itemId){
R.id.action_camera -> Log.e(TAG, "action_camera")
R.id.action_gallery -> Log.e(TAG, "action_gallery")
}
return#setNavigationItemSelectedListener true
}
}
}