I have a problem with fragments. I am trying to get data from a fragment to an activity. I am using a FragmentStatePagerAdapter in my Activity.
The fragment is as follow, it creates some view for a user.
class FormFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout._component_main_container, container, false)
...
UICreator.create(context, rootView.entry_container) //creation of the form view ok
...
return rootView
}
//public method to get the user input
fun getUserInputData() : List<Data> {
return CreationService.getUserInput(view!!.entry_container) //same result with rootView.entry_container
}
}
Here my adapter without the arguments passing and getCount, etc..
class ViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
val fragment = FormFragment()
return fragment
}
}
And in my activity I tried these to get the current fragment and to get the user data but nothing is working.
val fragment = supportFragmentManager.findFragmentByTag("android:switcher:" + R.id.view_pager + ":" + view_pager.currentItem) as FormFragment
fragment.getUserInputData() --> NULL
val fragment = viewPagerAdapter.getItem(view_pager.currentItem) as FormFragment
fragment.getUserInputData() --> NULL
Do you know a valid solution and effective to get the current fragment and the possibility to get a reference to the linear layout view that holding the user data?
Related
I have a fragment which contains a ViewPager2, It has 3 child fragments, I want to receive an reference viewpager in child fragment to be able to change viewPager programmatically. Since parent fragment which contains viewPager is not activity I can not access the viewPager with findViewById. On another question a usersuggested val pager = container as ViewPager2 but it's returning null. Can someone help me with this. As seen in code snippets, I am trying to get an reference to viewPager in BillingFragment from OneFragment.
Please let me know if you need to see ViewPagerAdapter code snippet.
Child fragment
class OneFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_billing, container, false)
val pager = container as ViewPager2
// ^^^^^ page is null
return view;
}
ViewPager main fragment
class BillingFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_billing_main, container, false)
setupStepView(view)
setupViewPager(view)
setupButtons(view)
return view
}
private fun setupStepView(view: View) {
view.stepView.state
// You should specify only stepsNumber or steps array of strings.
// In case you specify both steps array is chosen.
.steps(
listOf(
"First Step",
"Second Step",
"Third Step"
)
) // You should specify only steps number or steps array of strings.
// In case you specify both steps array is chosen.
.stepsNumber(3)
.animationDuration(resources.getInteger(android.R.integer.config_shortAnimTime))
.commit()
view.stepView.setOnStepClickListener { position ->
// viewPager.setCurrentItem(position, false)
}
}
private fun setupViewPager(view: View) {
// viewPager = view.viewPager;
view.viewPager.adapter = ViewPagerAdapter(childFragmentManager, lifecycle)
view.viewPager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
view.stepView.go(position, true)
setButtons(position, view)
}
}
)
}
private fun setupButtons(view: View) {
view.backButton.setOnClickListener {
view.viewPager.setCurrentItem(view.viewPager.currentItem - 1, false)
}
view.nextButton.setOnClickListener {
view.viewPager.setCurrentItem(view.viewPager.currentItem + 1, false)
}
}
private fun setButtons(position: Int, view: View) {
when (position) {
0 -> {
view.backButton.visibility = View.INVISIBLE
view.nextButton.visibility = View.VISIBLE
}
1 -> {
view.backButton.visibility = View.VISIBLE
view.nextButton.visibility = View.VISIBLE
}
2 -> {
view.backButton.visibility = View.VISIBLE
view.nextButton.visibility = View.INVISIBLE
}
}
}
}
Accessing the child fragment's view in parent fragment is not a good idea. If you want to trigger any action from the fragment. You can follow these steps:
class MyViewPagerAdapter(...) : FragmentStateAdapter(...){
lsit : List<Fragment>
fun getItem(position :Int) = list[position]
}
class OneFragment : Fragment(){
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_billing, container, false)
val position = viewPager2.currentItem
(adapter.getItem(position) as? BillingFragment)?.
return view;
}
}
class BillingFragment : Fragment(){
override fun onViewCreated(...){
...
}
// pass the data in the parameter that you want to use or update
fun updateViewPager(position : Int){
// perform your actions
view.viewPager.currentItem = position
}
}
Alternatives
If you know about EventBus, then you can use EventBus.
The android's default way is to use BroadcastReceiver.
I'm trying to write a data transfer from an activity to a fragment, but I'm catching "NullPointerException"
Code MainActivity:
val btn1 = findViewById<Button>(R.id.button)
btn1.setOnClickListener {
BlankFragment2.getNewInstance(321)
val fm = supportFragmentManager
val ft = fm.beginTransaction()
ft.replace(R.id.fragment, BlankFragment2())
ft.commit()
}
Code Fragment:
class BlankFragment2 : Fragment() {
var str: Int = 0
companion object {
fun getNewInstance(args: Int): BlankFragment2 {
val fragment = BlankFragment2()
fragment.arguments?.putInt("one", args)
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
str = arguments!!.getInt("one")
return inflater.inflate(R.layout.fragment_blank2, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val txt = view.findViewById<TextView>(R.id.textTest)
txt.text = str.toString()
}
}
I know about protection against null-values, and the problem is that I am sure that the value is passed and null cannot be there
instead of
ft.replace(R.id.fragment, BlankFragment2()
you should use
ft.replace(R.id.fragment, BlankFragment2.getNewInstance(321).
Because your BlankFragment2.getNewInstance(321) statement above is kind of useless to the fragmentManager. FragmentManager is creating fragment using BlankFragment2() as you provided the Fragment in the replace call.
And that is the reason for nullpointerexception because in reality, your Fragment didn't get any int value at all as the Fragment instance used was created with the empty constructor.
And also update your companion object code like below.
companion object {
fun getNewInstance(args: Int): BlankFragment2 {
val fragment = BlankFragment2()
val args = Bundle()
args.putInt("one", args)
fragment.setArguments(args)
return fragment
}
}
because right now your code is actually not setting argument but try to access argument and setting value to it.
Problem:
i have a layout like this:
in the parent fragment i have a searchview responsible for filtering the recyclerviews inside the child fragments.
the child fragments are inside a tablayout viewpager combination.
so the OnQueryTextListener is inside the parent fragment and i have to get an instance of the recyclerviews adapters from child fragments to do the filtering.
what is the proper way to do this?
What I've tried:
i searched and found getChildFramgentManager which returns an instance of the child fragment. but apparently, the fragments should be created dynamically? im not sure how that works with viewpager. so if there is a way to do this with getChildFramgentManager and viewpager please explain.
My code:
everything I've written is the standard procedure and im not that far into the project to add something that changes the outcome so im not gonna take your time by adding the code, but if the code is necessary please say so and i will add it.
In your viewpager's adapter you can store references to child fragments into a list and iterate through child fragments to call a method.
Something like this :
Parent fragment
class ParentFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.parent_fragment, container, false)
val search : SearchView = findViewById(R.id.search)
val pager : ViewPager = findViewById(R.id.pager)
val adapter = ViewPagerAdapter(supportFragmentManager)
pager.adapter = adapter
search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
adapter.filter(query ?: "")
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return true
}
})
return view
}
}
Pager adapter
class ViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
private var mList : MutableList<ChildFragment> = ArrayList()
init {
mList.add(ChildFragment())
mList.add(ChildFragment())
}
fun filter(text: String) {
mList.forEach {
it.filter(text)
}
}
override fun getItem(position: Int): Fragment {
return mList.get(position)
}
override fun getCount(): Int {
return mList.size
}
}
Child fragment
class ChildFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.child_fragment, container, false)
// get reference to recyclerview, set adapter...
return view
}
fun filter(text: String) {
// do job here
}
}
i am currently programming an app in Android Studio and i am having a big issue. The main problem is, that i want an activity with a fragment in it and this fragment has got a spinner. I wanted to find the spinner by id, but it always returned null and i read that i can't use findViewById if it is not in the ContentView i just set. So i am currently trying to find the fragment that contains the spinner, but i also can't find the fragment, i tried findFragmentById and findViewById from the FragmentManager. I always get a TypeCastException and if i try findFragmentById(...)!! it throws a NullPointer.
This is my MainActivity:
class MainActivity : AppCompatActivity() {
private val manager: FragmentManager? = supportFragmentManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showDeviceFragment()
val fragment = manager!!.findFragmentById(R.id.fragment_holder) as DeviceFragment
val options = arrayOf("Wandhalterung (Arm)", "Gestellhalterung (Arm)", "Gestellhalterung")
fragment.option.adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, options)
}
fun showDeviceFragment() {
val transaction = manager!!.beginTransaction()
val fragment = DeviceFragment()
transaction.add(R.id.fragment_holder, fragment, "DEVICE_FRAGMENT")
transaction.addToBackStack(null)
transaction.commit()
}
}
And this is the DeviceFragment:
class DeviceFragment : Fragment() {
lateinit var option : Spinner
companion object {
fun newInstance(): DeviceFragment {
return DeviceFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_device, container, false)
option = view.spinner
return inflater?.inflate(R.layout.fragment_device, container, false)
}
}
The fragment_holder just is a FrameLayout.
Thanks in advance
Few things:
1) No need to hold reference for supportFragmentManager, use it directly (because i am not sure if it will be null when Activity is initialized)
2) Try removing addToBackStack(null) and using findFragmentByTag("DEVICE_FRAGMENT")
3) Most importantly, Don't try to access "things" of Fragment from Activity, do those Adapter initialization/fill in the Fragment itself. Because Fragment has its own lifecycle and you may try to access "things" at wrong lifecycle
I'm trying to refresh the data in my fragment every time the user clicks on it in the Bottom Menu Navigation. I already wrote an interface which gets called each time the fragment gets selected by the user
The problem is that the method inside my fragment has no access to the view of the fragment (I guess):
MainMenu
viewPager = findViewById(R.id.frame_container)
viewPager!!.offscreenPageLimit = 5
viewPager!!.orientation = ViewPager2.ORIENTATION_HORIZONTAL
viewPager!!.adapter = pageAdapter
viewPager!!.currentItem = 0
viewPager!!.isUserInputEnabled = false
viewPager!!.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
if(position == 2) {
val mFragment = ListFragment()
mFragment.ready(this#MainMenu)
}
}
})
The Interface "Ready":
interface ReadyInterface {
fun ready(activity: FragmentActivity?)
}
and the ListFragment:
class ListFragment: Fragment(), ReadyInterface {
var mView : View? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_list, container, false)
this.mView = view
val user = FirebaseAuth.getInstance().currentUser
return if (user != null) {
view
} else {
val unregisteredView = inflater.inflate(R.layout.fragment_unregistred, container, false)
val registerNow = unregisteredView.findViewById<TextView>(R.id.textview_registernow)
unregisteredView
}
override fun ready(activity: FragmentActivity?) {
testText = mView!!.findViewById(R.id.test_text)
testText.text = "Test Text here"
Toast.makeText(activity!!.applicationContext,"Test",Toast.LENGTH_LONG).show()
}
This code here crashes with a "kotlin.KotlinNullPointerException" on line "testText.text = ...."
So I guess the fun ready hasn't got access to the view of my fragment because of the fragments lifecycle, am I right? How could I fix this?
There is no need to implement interface here. You can directly make ready() a member function of ListFragment and call it from onPageSelected(position: Int) in MainMenuActivity. Currently, the interface method is being called before onCreateView() causing it to throw null pointer exception as the view is not initialised yet.
You are creating a new instance of ListFragment() while calling ready on it. This new fragment is not attached to the ViewPager. This new instance also has not gone through any lifecycle methods, therefore, it's view is not yet initialized. Due to this, you get a NullPointerException while calling methods on a view.
To solve this you can instead fetch the current fragment from the ViewPager and call your method on it.