Why do buttons in Fragments to not get triggered? I've already spent about 5 hours on this, but no onClick / onFocusListener will trigger
Edit: Code
So this Fragment(s) is on a ViewPager2 on my MainActivity and
it just wont trigger any Callback Methods. I have also tried to but those onClick initializations everywhere
Fragment:
class SearchFragment : Fragment() {
private lateinit var searchBinding: FragmentSearchBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
searchBinding = FragmentSearchBinding.inflate(layoutInflater)
interface OnSearch {
fun onArticleSelected(position: Int)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_search, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var test = activity?.findViewById<TabLayout>(R.id.tab_layout)
searchBinding.searchBar.setOnFocusChangeListener{view, hasFocus ->
if (hasFocus){
if (test != null) {
test.isVisible = false
}
Log.d("SearchFragment", "onFocus")
}else{
if (test != null) {
test.isVisible = true
}
}
}
}
}
XML:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.main.search.SearchFragment"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.button.MaterialButton
android:id="#+id/testButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:id="#+id/search_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:hint="Search"
android:focusedByDefault="true"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/searchRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</FrameLayout>
Try something like this
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) {
super.onCreateView(view, savedInstanceState)
val binding = FragmentSearchBinding.inflate(inflater, container, false);
val view = binding.root
// Set binding
return view;
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById(R.id.searchBar).setOnFocusChangeListener { view, hasFocus ->
}
}
Related
I have simple DialogWindow
XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="300dp"
android:layout_height="150dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<fragment
android:id="#+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/navigation"/>
</LinearLayout>
Code:
class DialogWindowBuyingStock(context: Context, private val viewModel: MainViewModel, private val stock: Stock) : AppCompatDialog(context) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(R.layout.buy_stock_dialogwindow)
}
}
In which i have some fragments for example the Main one:
class MainFragment : Fragment() {
private var _binding: MainFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val homeViewModel =
ViewModelProvider(this)[MainFragmentViewModel::class.java]
_binding = MainFragmentBinding.inflate(inflater, container, false)
val root: View = binding.root
return root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
main_buy_button.setOnClickListener {
Navigation.findNavController(view).navigate(R.id.action_mainFragment_to_buyFragment)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
It works perfectly for the first time i start Dialog Window, but when i close it and start again, i got this llegalArgumentException: Binary XML file line #20: Duplicate id 0x7f080136, tag null, or parent id 0xffffffff with another fragment for androidx.navigation.fragment.NavHostFragment
How can i fix that?
First Fragment's code
class MainFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val mainInflater = inflater.inflate(R.layout.fragment_main, container, false)
return mainInflater
}
fun thisdata():String{
return "Hello from MainFragment"
}
}
First Fragment XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainFragment">
<EditText
android:id="#+id/etSavedData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="60dp"
android:hint="Enter the Text"/>
<Button
android:id="#+id/btnSaveData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="120dp"
android:text="Save"/>
</RelativeLayout>
Main Activity's code
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Set replace Main Activity content with the Fragment1 content
val mainFragment = MainFragment()
supportFragmentManager.beginTransaction().add(R.id.contain_fragment, mainFragment).commit()
val thedata = mainFragment.thisdata()
Log.e("Main Frag to Activity", thedata)
btnSaveData.setOnClickListener {
val secondFragment = SecondFragment()
supportFragmentManager.beginTransaction().add(R.id.contain_fragment, secondFragment).commit()
}
}
}
Main Activity XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:id="#+id/contain_fragment"
tools:context=".MainActivity">
</RelativeLayout>
Second Fragment's code
class SecondFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val secondInflater = inflater.inflate(R.layout.fragment_second, container, false)
return secondInflater
}
}
Second Fragment XML
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/holo_blue_dark"
tools:context=".SecondFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="#+id/tvDisplayText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Text displayed here." />
</FrameLayout>
Following Exception showing up in Logcat
Caused by: java.lang.NullPointerException: Attempt to invoke virtual
method 'void
android.widget.Button.setOnClickListener(android.view.View$OnClickListener)'
on a null object reference
at com.example.demo2.MainActivity.onCreate(MainActivity.kt:22)*
You either need to move your btnSaveData button into the MainActivity, or you should move the onClickListener into the MainFragment, along with some other changes. Something like:
class MainFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val mainInflater = inflater.inflate(R.layout.fragment_main, container, false)
btnSaveData.setOnClickListener {
val secondFragment = SecondFragment()
activity.supportFragmentManager.beginTransaction().add(R.id.contain_fragment, secondFragment).commit()
}
return mainInflater
}
fun thisdata():String{
return "Hello from MainFragment"
}
}
**Thanks Tash, that actually make sense, unfortunately it was still showing the same exception but I finally manage to resolve the issue by doing the following. **
class MainFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val mainInflater = inflater.inflate(R.layout.fragment_main, container, false)
mainInflater.btnSaveData.setOnClickListener {
val secondFragment = SecondFragment()
secondFragment?.let {
activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.contain_fragment, secondFragment)?.commit()
}
}
return mainInflater
}
fun thisdata():String{
return "Hello from MainFragment"
}
}
Hi I have a strange problem. MainActivity has a framelayout and a main fragment EnterFragment.
EnterFragment has a framelayout and i'd like to see other 2 fragments. SignupFragment and LoginFragment.
When EnterFragment starts, it shows SignupFragment.java.. after MainActivity invoke a method of EnterFragment to change fragment to LoginFragment but app crash:
java.lang.IllegalStateException: Fragment EnterFragment{95bc7de (70f980b2-eff8-4e4c-a644-e95544b73584)} has not been attached yet.
at androidx.fragment.app.Fragment.getChildFragmentManager(Fragment.java:923)
Here the project: https://www.dropbox.com/s/qq1kl4qw688ipjr/example.zip?dl=0
Here the code:
//MainActivity
class MainActivity : AppCompatActivity() {
private var enterFragment: EnterFragment?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showEnter()
}
private fun showEnter() {
enterFragment= EnterFragment.newInstance()
this.replaceFragmentTransition(R.id.fragmentContainer, EnterFragment.newInstance(), EnterFragment.TAG )
var handler= Handler()
handler.postDelayed(Runnable {gotoLoginFromSignup() }, 8000)
}
private fun gotoLoginFromSignup() {
enterFragment?.gotoLogin()
}
}
//EnterFragment
class EnterFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_enrer, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
this.replaceFragmentTransition(R.id.fmContainer, SignuFragment.newInstance(), SignuFragment.TAG )
}
fun gotoLogin(){
this.replaceFragmentTransition(R.id.fmContainer, LoginFragment.newInstance(), SignuFragment.TAG )
}
companion object {
val TAG: String="EnterFragment"
#JvmStatic
fun newInstance() = EnterFragment()
}
}
//SignupFragment
class SignuFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_signup, container, false)
}
companion object {
val TAG: String="SignuFragment"
#JvmStatic
fun newInstance() = SignuFragment()
}
}
//LoginFragment
class LoginFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_login, container, false)
}
companion object {
val TAG: String="LoginFragment"
#JvmStatic
fun newInstance() = LoginFragment()
}
}
//EXTENSIONS
fun FragmentActivity.replaceFragmentTransition(
container: Int,
fragment: Fragment?,
tag: String
) {
fragment?.let {
var transition = this.supportFragmentManager
.beginTransaction()
transition.replace(container, it, tag)
.commitNowAllowingStateLoss()
}
}
fun Fragment.replaceFragmentTransition(
container: Int,
fragment: Fragment?,
tag: String
) {
fragment?.let {
var transition = this.childFragmentManager
.beginTransaction()
transition.replace(container, it, tag)
.commitNowAllowingStateLoss()
}
}
//main activity layout
<androidx.constraintlayout.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"
tools:context=".MainActivity">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragmentContainer"
android:layout_width="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_height="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
//enterFragment
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".EnterFragment">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fmContainer"
android:layout_width="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_height="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
Replace this
this.replaceFragment(R.id.fragmentContainer, FatherFragment.newInstance(), FatherFragment.TAG )
with
this.replaceFragment(R.id.fragmentContainer, fatherFragment!!, FatherFragment.TAG )
while transaction, you are passing different instance & calling openLogin() with another instance.
FatherFragment.newInstance() will give new instance.
After rotation, one old page is displayed, followed by new pages.
Before rotation 1.
After rotation 2.
Layout with ViewPager:
<RelativeLayout ...
<androidx.viewpager.widget.ViewPager
android:id="#+id/pager"
android:overScrollMode="never"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="10dp"
android:layout_height="match_parent"
android:layout_width="match_parent">
</RelativeLayout>
Fragment with this layout:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = inflater.inflate(layoutId(), container, false)
view.findViewById<ViewPager>(R.id.pager).adapter = TextPagerAdapter(activity!!.supportFragmentManager, listOf())
return view
}
TextPagerAdapter:
override fun getItem(position: Int): Fragment = PageFragment.newInstance("sssssss ssss $position")
override fun getCount() = 5//pageText.size
}
PageFragment:
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {
val view = inflater.inflate(layoutId(), container, false)
view.findViewById<TextView>(R.id.page_content_textView).text = arguments!!.getCharSequence(PAGE_TEXT)
return view
}
I forgot to check if the content fragment is already created.
The solution is to check if the savedInstanceState == null. And create new fragment if that condition is true.
In MyActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(savedInstanceState == null) this.supportFragmentManager.beginTransaction()
.add(containerViewId, CollectionDemoFragment()).commit()
}
The following code is based by Android Studio Wizard 3.2.1 for creating Tabbed Activity.
There is a button1 on PlaceholderFragment1 connected Tab1, and a button2 on PlaceholderFragment2 connected Tab2
I hope to click button1 to switch to Tab2, and click button2 to switch to Tab1, How can I do ?
BTW, I have read How to change tab on button click in Android? and How to programmatically switch tabs using buttonclick in Android
Code
class MainActivity : AppCompatActivity() {
private var mSectionsPagerAdapter: SectionsPagerAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mSectionsPagerAdapter = SectionsPagerAdapter(supportFragmentManager)
container.adapter = mSectionsPagerAdapter
container.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs))
tabs.addOnTabSelectedListener(TabLayout.ViewPagerOnTabSelectedListener(container))
}
inner class SectionsPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
val fragment: Fragment
when (position) {
0 -> fragment = PlaceholderFragment1() //Tab1
1 -> fragment = PlaceholderFragment2() //Tab2
else -> throw IllegalArgumentException("Invalid section number")
}
return fragment
}
override fun getCount(): Int {
return 2
}
}
class PlaceholderFragment1 : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.fragment_main1, container, false)
return rootView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
button1.setOnClickListener {
//Switch to Tab2
}
}
}
class PlaceholderFragment2 : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.fragment_main2, container, false)
return rootView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
button2.setOnClickListener {
//Switch to Tab1
}
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="#dimen/appbar_padding_top"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.TabItem
android:id="#+id/tabItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/tab_text_1"/>
<android.support.design.widget.TabItem
android:id="#+id/tabItem2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/tab_text_2"/>
</android.support.design.widget.TabLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
fragment_main1.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:id="#+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:text="Button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/button1"/>
</android.support.constraint.ConstraintLayout>
My way
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
setControl(view)
}
private fun setControl(view: View){
button1.setOnClickListener {
var viewPager=view.parent as ViewPager
viewPager.setCurrentItem(1,true)
}
}
manually binding these events is not required...
you just have to setup the TabLayout with the Viewpager:
container.adapter = mSectionsPagerAdapter
tabs.setupWithViewPager(container);
then these events are being handled by the framework, in a rather convenient way.
PS: container is a rather unfortunate variable name for a ViewPager.
I think you should use Livedata and ViewModel.
Create View Model class with livedata.
Observer Livedata in MainActivity.
Update livedata value from your fragment.
For example:
class TabChangerViewModel : ViewModel() {
val colorResource = MutableLiveData<Int>()
.........
}
In your MainActivity class:
val tabChangerViewModel = ViewModelProviders.of(this).get(TabChangerViewModel::class.java) // initialize view model
tabChangerViewModel.colorResource.observe(this, android.arch.lifecycle.Observer {
//mViewPager.setCurrentItem(it)
})
Now update live data value from your fragment:
val tabChangerViewModel = ViewModelProviders.of(activity).get(TabChangerViewModel::class.java) // initialize view model
tabChangerViewModel.colorResource.value = TAB_POSITION_WANT_TO_SELECT