So I have the following ImageButton in a fragment:
<ImageButton
android:id="#+id/moneyBtn"
style="#android:style/Widget.Holo.Light.ImageButton"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:src="#drawable/monkey"
android:background="#null"/>
And the following fragmentActivity.kt
class Home : Fragment() {
override public fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View? = inflater.inflate(R.layout.fragment_home, container, false)
val moneyButton: ImageButton = view?.findViewById(R.id.moneyBtn) as ImageButton
val result = MyAppApplication()
var money = result.money
moneyButton.setOnClickListener(View.OnClickListener {
Toast.makeText(activity, "TESTING BUTTON CLICK 1", Toast.LENGTH_SHORT).show()
})
return view
}
I also tried to use the "normal" Kotline setOnClickListener
moneyButton.setOnClickListener {
Toast.makeText(activity, "TESTING BUTTON CLICK 1", Toast.LENGTH_SHORT).show()
}
The App dosent crash and dosent freeze, it just dont work
I also tried to replace the Toast with a throw, but that wont be exceuted either.
Maybe you can find my mistake?
Try initializing your click listener in onActivityCreated. It's called after onCreateView so it'll ensure that view is inflated.
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val moneyButton: ImageButton = activity.findViewById(R.id.moneyBtn) as ImageButton
moneyButton.setOnClickListener {
Toast.makeText(activity, "TESTING BUTTON CLICK 1", Toast.LENGTH_SHORT).show()
}
}
Had the same problem and it can be done like following codes:
In your Fragment:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.fragment_home, container, false)
val imgViewButton = rootView.findViewById<ImageButton>(R.id.moneyBtn) // Use the current view
imgViewButton?.setOnClickListener() {
Toast.makeText(activity, "Clicked!", Toast.LENGTH_SHORT).show()
}
return rootView
}
The reason is the View which we weren't get it in the right way. But, with these codes, it sets the current View and then after all, returns the view which works perfectly.
Strange, I just tried your code snippets on my platform, and the Toast pops up just fine... Try changing the first arg of Toast to view.context instead so it'd look something like:
Toast.makeText(view.context, "TESTING BUTTON CLICK 1", Toast.LENGTH_SHORT).show()
Let me know if that makes a difference.
First of all, do you even know that this code is executed? That the listener is set? Maybe try to use the Log class (should be the same in Kotlin as in Java).Also,
val result = MyAppApplication()
var money = result.money
looks suspicious to me. Are you trying to create a new Application instance?
Related
I know there is a lateinit or lazy keyword in Kotlin to prevent indiscriminate initialization and thus minimize wasted resources.
I wanted to use the lazy keyword to use findViewById when necessary events occur.
However, if I use the lazy keyword, nothing happens. It doesn't even cause an error.
Conversely, when findViewId is normally used in onCreateView, click event occurs normally.
Why doesn't lazy work?
class BodyPartDialogFragment : DialogFragment(), View.OnClickListener{
private val ll: LinearLayout? by lazy { view?.findViewById(R.id.ll_body_part) }
// private lateinit var button: Button
private val button: Button? by lazy { view?.findViewById(R.id.start) }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_body_part_dialog, container, false)
// ll = view.findViewById(R.id.ll_body_part)
// button = view.findViewById(R.id.start)
ll?.apply { clipToOutline = true }
button?.setOnClickListener { // Nothing Happened
Toast.makeText(context, "Noting Selected", Toast.LENGTH_SHORT).show()
}
return view
}
getView() that is behind the view property returns whatever you returned from onCreateView(). When you access view inside onCreateView(), it hasn't yet returned anything and hence a null is returned, and your ?. safecall becomes a no-op.
You can use a lazy approach like this after onCreateView(), such as in onViewCreated().
It looks like you may be initializing things in the wrong order.
Consider that renaming a local variable always preserves semantics, so let's modify your code a little:
private val ll: LinearLayout? by lazy { view?.findViewById(R.id.ll_body_part) }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val someRandomView: View = inflater.inflate(R.layout.fragment_body_part_dialog, container, false)
ll?.apply { clipToOutline = true }
button?.setOnClickListener { // Nothing Happened
Toast.makeText(context, "Noting Selected", Toast.LENGTH_SHORT).show()
}
return someRandomView
}
Do you see the issue? ll is being initialized with a view that hasn't been assigned yet in onCreateView.
view (or really getView()) is the view that is returned from onCreateView(). You're trying to access that before you have returned from onCreateView() so it returns null, and your lazy value is then also null. You can make this work by accessing it later, ie. in onViewCreated()
class BodyPartDialogFragment : DialogFragment(), View.OnClickListener{
private val ll: LinearLayout? by lazy { view?.findViewById(R.id.ll_body_part) }
// private lateinit var button: Button
private val button: Button? by lazy { view?.findViewById(R.id.start) }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_body_part_dialog, container, false)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
ll?.apply { clipToOutline = true }
button?.setOnClickListener { // Nothing Happened
Toast.makeText(context, "Noting Selected", Toast.LENGTH_SHORT).show()
}
}
}
This is more clear if you use requireView() since it returns a non-null View and rather throws an exception, so your app would have crashed with the error message did not return a View from onCreateView() or this was called before onCreateView().
You can do this to get access to View in the future using 'by lazy'
private val previewImage by lazy { requireActivity().findViewById<ImageView>(R.id.ivImage) }
Then you can use it like
previewImage.setImageURI(imageUri)
Hi I've been having issue with making a click work in a fragment. (e.g. Toast, button, or anything related to click)
For instance, I'm trying to implement Toast.makeText in the code below.
xml file mentioned in this code is an image gallery and I want my app to display a toast for each image to show what the image is about.
I've been trying many different things and have been searching for 10 hourish, but nothing seemed to work. Plus, most sources related to this question were for Java only. Any help or suggestion will very much be appreciated.
class GalleryFragment : Fragment(), View.OnClickListener {
var myButton: ImageView? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val myView: View = inflater.inflate(R.layout.fragment_gallery, container, false)
myButton = myView.findViewById<View>(R.id.image1) as ImageView
myButton!!.setOnClickListener(this)
return myView
}
override fun onClick(v: View) {
Toast.makeText(activity?.applicationContext, "Beach", Toast.LENGTH_SHORT).show()
}
}
Your code looks Ok.
Please check if the image on which you are expecting the click to show a toast is not too small that it is not physically clickable. maybe you have the image view of the size 16dp or even less. Or you may have set the android:clickable to false.
If you could add the layout file, it would be easier to find the issue.
if onClick you checked it and it is tiggered, change where it sais: activity?.applicationContext with v.context and tell me if that works. Maybe you are getting wrong context.
From fragments this is how I generate Toasts (with either requireActivity() or requireContext()). You might also want to move the attaching of the listener to onViewCreated():
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myButton = myView.findViewById<View>(R.id.image1) as ImageView
myButton!!.setOnClickListener(this)
}
override fun onClick(v: View) {
//here is the change
Toast.makeText(requireActivity(), "Beach", Toast.LENGTH_SHORT).show()
}
You could also create an extension function:
fun Context.shortToast(toastMessage: String){
Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT ).show()
}
//call this in the fragment
requireContext().shortToast("Here is my toast message")
The context should not be nullable type. The error shows that it is a type mismatch.
Toast.makeText(context, "saving", Toast.LENGTH_SHORT).show()
The !! (not-null assertion operator) is used to denote the variable is not null.
Using let and safe calls
context?.let{ context->
Toast.makeText(context, "saving", Toast.LENGTH_SHORT).show()
}
I am trying to hide one specific button at the bottom of my layout when the keyboard is opened, in order to make more view available for the user.
With the release of androidx.core:core-ktx:1.5.0-alpha02 google (finally) added a method called insets.isVisible(WindowInsetsCompat.Type.ime()) which returns a boolean whether the keyboard is opened or clicked.
I am using a base class EmailFragment where I set the function in order to achieve the above written functionality. My problem is that my ViewCompat.setOnApplyWindowInsetsListener(view) gets never called (no toast etc).
I've also tried to set ViewCompat.setOnApplyWindowInsetsListener(view) directly in the used Fragments, but that changed nothing.
My min API is 21, in my AndroidManifest.XML I have android:windowSoftInputMode = adjustResize
Code
abstract class EmailFragment<out T: ViewDataBinding>(
layout: Int,
// ... some other stuff that is not necessary for the question
) : BaseFragment<T>(layout) {
// ... some other stuff that is not necesarry for the question
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
hideButton(view)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
private fun hideButton(view: View) {
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val isKeyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
if (isKeyboardVisible) {
btn.visibility = View.GONE
Toast.makeText(requireContext(), "KEYBOARD OPEN", Toast.LENGTH_SHORT).show()
} else {
btn.visibility = View.VISIBLE
Toast.makeText(requireContext(), "KEYBOARD CLOSED", Toast.LENGTH_SHORT).show()
}
// Return the insets to keep going down this event to the view hierarchy
insets
}
}
}
Fragment that inherits from EmailFragment (one out of five)
class CalibrateRepairMessageFragment(
//... some other stuff that is not necessary for this question
) : EmailFragment<FragmentCalibrateRepairMessageBinding>(
R.layout.fragment_calibrate_repair_message,
//... some other stuff that is not necessary for this question
) {
//... some other stuff that is not necessary for this question
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//... some other stuff that is not necessary for this question
}
Screenshot 1 (censored)
Screenshot 2, not working (censored)
I know that using android:windowSoftInputMode = adjustPen makes my button "invisible" but then I cannot scroll anymore, big sad..
Another solution could be that the keyboard just overlaps the button, but I have no clue how to do that...
I appreciate every help, thank you.
I was able to get this to work by:
Adding android:windowSoftInputMode="adjustResize" to Activity tag in manifest
Setting OnApplyWindowInsetsListener on window.decorView.
However, this had unfortunate side effects to the statusbar which means that OP's comment ("I have no hope that this will ever work") is probably accurate. Hopefully it will work when it graduates from alpha and beta.
I am in an activity fragment whereby i want to display a toast widget after the commands for the submit button have been met and completed.
the code:
class HomeFragment : Fragment() {
private val currentUserDocRef = Firebase.firestore.collection("users")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_home, container, false)
view.apply {
submitbutton.setOnClickListener {
FirestoreUtil.updateCurrentUser(
edittextPersonname.text.toString(),
editTextBio.text.toString(),
editTextTextEmailAddress.text.toString(),
edittextage.text.toString()
)
}
return view
}
}
no error is present in my code however on trying to declare a toast widget i get an error. code:
Toast.makeText(this#HomeFragment, "saving", Toast.LENGTH_SHORT).show()
the error:
You need a context to show toast, here is the code :
Toast.makeText(this#HomeFragment.requireActivity(), "saving", Toast.LENGTH_SHORT).show()
Thanks
The context should not be nullable type. The error shows that it is a type mismatch.
Option 1:
Toast.makeText(context!!, "saving", Toast.LENGTH_SHORT).show()
The !! (not-null assertion operator) is used to denote the variable is not null.
Option 2:
Using let and safe calls
context?.let{ context->
Toast.makeText(context, "saving", Toast.LENGTH_SHORT).show()
}
Refer: https://kotlinlang.org/docs/reference/null-safety.html for more details
In my application i want show message when fragment has show.
I used viewPager and BottomNavBar for show 4 fragments!
I want when click on BottomNavBar items show fragment and i want when visibility fragment show message.
I write below codes :
class HomeRegisteredFragment : Fragment() {
lateinit var toolbarTile: TextView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_home_registered, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Initialize
activity?.let {
toolbarTile = it.findViewById(R.id.homePage_toolbarTitle)
}
//Set title
toolbarTile.text = resources.getString(R.string.registered)
context?.let { ContextCompat.getColor(it, R.color.blue_active) }?.let {
toolbarTile.setTextColor(it)
}
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser) {
Log.e("showFragLog", "Show")
context?.let { Toast.makeText(it, "Show", Toast.LENGTH_SHORT).show() }
}
}
}
In my above codes, when click on my BottomNavBar for show fragment, show me Log message but not show Toast message.
When click on another BottomNavBar items and again click on previous BottomNavBar item, then show Toast message.
I think in first time not initialize context in setUserVisibleHint method.
How can i initialize context for show Toast in every time?
I changed your codes with below codes :
class HomeRegisteredFragment : Fragment() {
lateinit var toolbarTile: TextView
lateinit var handler: Handler
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_home_registered, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Initialize
activity?.let {
toolbarTile = it.findViewById(R.id.homePage_toolbarTitle)
}
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser) {
//Initialize
handler = Handler()
//Set delay
handler.postDelayed({
Toast.makeText(requireContext(),"Show",Toast.LENGTH_SHORT).show()
}, 10)
}
}
}
First you should use requireContext() instead of context() for avoid from memory leak.
For show Toast for every time, you can initialize handler in setUserVisibleHint , then after some delay run your code!
I hope help you
Storing context in a variable is a horrible practive and most of the times leads to memory leaks, use requireContext() this method was introduced in Support Library 27.1.0. Nowdays most likely you will have a newer version or even using androidx so there is no excuse for storing a context
If you are looking for application context to show the toast message, try the below way and see if it works. Also, initialize it onCreate method so you have the activity context at that point.
val appContext = context!!.applicationContext
O have a similar trouble here. I have one Activity with multiple Fragments, and I need a ListView to show some employes.
But when I call the Adapter class, I don't know how to pass the context variable:
binding.listviewCoordenacoes.isClickable = true
binding.listviewCoordenacoes.adapter = CoordenadorAdapter(requireContext().applicationContext as Activity, arrayListCoordenador)
binding.listviewCoordenacoes.setOnClickListener{}
In the examples in general, it works in Activities. If not possible, I will create an Activity and put it in that.