androidandroid-fragmentskotlinandroid-viewpagerfragmentpageradapter

Getting user-input information back from viewPager fragments (Kotlin)


I'm working in Android Studio and I've run into an issue I've never encountered before.

I'm making an interface that brings in JSON to create custom forms and creates a series of tab-controlled fragments with edit texts and date pickers and similar. These forms are created on a website; and the contents aren't known until the JSON pulls them in.

I've setup a SectionsPagerAdapter to handle creating the sections and placeholder fragments to create the various pickers and edit texts. I'm auto-generating the view IDs with ViewCompat.generateViewId().

Currently, I'm using mock JSON data to test, and everything works great. The user is able to enter information and affect change; and the changes persist as the user navigates across the different section tabs, regardless of how many are there.

The issue I'm having is in trying to get the input data from all of the fragments upon hitting a floating action button. I just haven't figured out how I could access the data from the fragments from the activity level.

Here's the code below:

CreateInspectionActivity

class CreateInspectionActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_create_inspection)

    //List of items to mock what I would receive as JSON
    val mockJSON = JSONArray()
    mockJSON.put(Pair("General", arrayListOf(
        Pair("General EditText", "editText"), Pair("General Date", "datePicker"),
        Pair("General Time", "timePicker"), Pair("General EditText 2", "editText"),
        Pair("General Rating", "rating"), Pair("General Rating 2", "rating"),
        Pair("General Rating 3", "rating"), Pair("General Rating 4", "rating"),
        Pair("General Rating 5", "rating"), Pair("General Rating 6", "rating"),
        Pair("General EditText 3", "editText"), Pair("General EditText 4", "editText"),
        Pair("General EditText 5", "editText"), Pair("General Date 2", "datePicker"),
        Pair("General Rating 7", "rating"), Pair("General Time 2", "timePicker"))))
    mockJSON.put(Pair("Secondary", arrayListOf(
        Pair("Secondary EditText", "editText"), Pair("Secondary EditText2", "editText"),
        Pair("Secondary EditText 3", "editText"), Pair("Secondary Date", "datePicker"),
        Pair("Secondary Time", "timePicker"),  Pair("Secondary Rating", "rating"),
        Pair("Secondary Rating 2", "rating"), Pair("Secondary Rating 3", "rating"),
        Pair("Secondary Rating 4", "rating"), Pair("Secondary Rating 5", "rating"))))
    mockJSON.put(Pair("Tertiary", arrayListOf(
        Pair("Tertiary Rating", "rating"), Pair("Tertiary Rating 2", "rating"),
        Pair("Tertiary Rating 3", "rating"), Pair("Tertiary Rating 4", "rating"),
        Pair("Tertiary Rating 5", "rating"), Pair("Tertiary Rating 6", "rating"),
        Pair("Tertiary Rating 7", "rating"), Pair("Tertiary Rating 8", "rating"),
        Pair("Tertiary Rating 9", "rating"), Pair("Tertiary Rating 10", "rating"))))
    mockJSON.put(Pair("Quaternary", arrayListOf(
        Pair("Quaternary Rating", "rating"), Pair("Quaternary Rating 2", "rating"),
        Pair("Quaternary Rating 3", "rating"), Pair("Quaternary Rating 4", "rating"),
        Pair("Quaternary Rating 5", "rating"), Pair("Quaternary Rating 6", "rating"),
        Pair("Quaternary Rating 7", "rating"), Pair("Quaternary Rating 8", "rating"),
        Pair("Quaternary Rating 9", "rating"), Pair("Quaternary Rating 10", "rating"),
        Pair("Quaternary Rating 11", "rating"), Pair("Quaternary Rating 12", "rating"),
        Pair("Quaternary Rating 13", "rating"), Pair("Quaternary Rating 14", "rating"),
        Pair("Quaternary Rating 15", "rating"), Pair("Quaternary Rating 16", "rating"),
        Pair("Quaternary Rating 17", "rating"), Pair("Quaternary Rating 18", "rating"),
        Pair("Quaternary Rating 19", "rating"), Pair("Quaternary Rating 20", "rating"))))
    mockJSON.put(Pair("Quinary", arrayListOf(
        Pair("Quinary Rating", "rating"), Pair("Quinary Rating 2", "rating"),
        Pair("Quinary Rating 3", "rating"), Pair("Quinary Rating 4", "rating"),
        Pair("Quinary Rating 5", "rating"), Pair("Quinary Rating 6", "rating"),
        Pair("Quinary Rating 7", "rating"), Pair("Quinary Rating 8", "rating"),
        Pair("Quinary Rating 9", "rating"), Pair("Quinary Rating 10", "rating"),
        Pair("Quinary Rating 11", "rating"), Pair("Quinary Rating 12", "rating"),
        Pair("Quinary Rating 13", "rating"), Pair("Quinary Rating 14", "rating"),
        Pair("Quinary Rating 15", "rating"), Pair("Quinary Rating 16", "rating"),
        Pair("Quinary Rating 17", "rating"), Pair("Quinary Rating 18", "rating"),
        Pair("Quinary Rating 19", "rating"), Pair("Quinary Rating 20", "rating"))))
    mockJSON.put(Pair("Senary", arrayListOf(
        Pair("Senary Rating", "rating"), Pair("Senary Rating 2", "rating"),
        Pair("Senary Rating 3", "rating"), Pair("Senary Rating 4", "rating"),
        Pair("Senary Rating 5", "rating"), Pair("Senary Rating 6", "rating"),
        Pair("Senary Rating 7", "rating"), Pair("Senary Rating 8", "rating"),
        Pair("Senary Rating 9", "rating"), Pair("Senary Rating 10", "rating"),
        Pair("Senary Rating 11", "rating"), Pair("Senary Rating 12", "rating"),
        Pair("Senary Rating 13", "rating"), Pair("Senary Rating 14", "rating"),
        Pair("Senary Rating 15", "rating"), Pair("Senary Rating 16", "rating"),
        Pair("Senary Rating 17", "rating"), Pair("Senary Rating 18", "rating"),
        Pair("Senary Rating 19", "rating"), Pair("Senary Rating 20", "rating"))))
    mockJSON.put(Pair("Septenary", arrayListOf(
        Pair("Septenary Rating", "rating"), Pair("Septenary Rating 2", "rating"),
        Pair("Septenary Rating 3", "rating"), Pair("Septenary Rating 4", "rating"),
        Pair("Septenary Rating 5", "rating"), Pair("Septenary Rating 6", "rating"),
        Pair("Septenary Rating 7", "rating"), Pair("Septenary Rating 8", "rating"),
        Pair("Septenary Rating 9", "rating"), Pair("Septenary Rating 10", "rating"),
        Pair("Septenary Rating 11", "rating"), Pair("Septenary Rating 12", "rating"),
        Pair("Septenary Rating 13", "rating"), Pair("Septenary Rating 14", "rating"),
        Pair("Septenary Rating 15", "rating"), Pair("Septenary Rating 16", "rating"),
        Pair("Septenary Rating 17", "rating"), Pair("Septenary Rating 18", "rating"),
        Pair("Septenary Rating 19", "rating"), Pair("Septenary Rating 20", "rating"))))
    mockJSON.put(Pair("Octonary", arrayListOf(
        Pair("Octonary Rating", "rating"), Pair("Octonary Rating 2", "rating"),
        Pair("Octonary Rating 3", "rating"), Pair("Octonary Rating 4", "rating"),
        Pair("Octonary Rating 5", "rating"), Pair("Octonary Rating 6", "rating"),
        Pair("Octonary Rating 7", "rating"), Pair("Octonary Rating 8", "rating"),
        Pair("Octonary Rating 9", "rating"), Pair("Octonary Rating 10", "rating"),
        Pair("Octonary Rating 11", "rating"), Pair("Octonary Rating 12", "rating"),
        Pair("Octonary Rating 13", "rating"), Pair("Octonary Rating 14", "rating"),
        Pair("Octonary Rating 15", "rating"), Pair("Octonary Rating 16", "rating"),
        Pair("Octonary Rating 17", "rating"), Pair("Octonary Rating 18", "rating"),
        Pair("Octonary Rating 19", "rating"), Pair("Octonary Rating 20", "rating"))))
    mockJSON.put(Pair("Nonary", arrayListOf(
        Pair("Nonary Rating", "rating"), Pair("Nonary Rating 2", "rating"),
        Pair("Nonary Rating 3", "rating"), Pair("Nonary Rating 4", "rating"),
        Pair("Nonary Rating 5", "rating"), Pair("Nonary Rating 6", "rating"),
        Pair("Nonary Rating 7", "rating"), Pair("Nonary Rating 8", "rating"),
        Pair("Nonary Rating 9", "rating"), Pair("Nonary Rating 10", "rating"),
        Pair("Nonary Rating 11", "rating"), Pair("Nonary Rating 12", "rating"),
        Pair("Nonary Rating 13", "rating"), Pair("Nonary Rating 14", "rating"),
        Pair("Nonary Rating 15", "rating"), Pair("Nonary Rating 16", "rating"),
        Pair("Nonary Rating 17", "rating"), Pair("Nonary Rating 18", "rating"),
        Pair("Nonary Rating 19", "rating"), Pair("Nonary Rating 20", "rating"))))

    //Setup Adapter for different tabs
    //This creates the pager adapter with the mock JSON array to create a section tab for each,
    // and to send the associated information on to the Placeholder fragment
    //Setup adapter for view pager
    val sectionsPagerAdapter = SectionsPagerAdapter(this, supportFragmentManager, mockJSON)
    val viewPager: ViewPager = findViewById(R.id.view_pager)
    //Set this page limit to keep data across multiple tabs. This must be set to the number of tabs there'll be, minus 1
    viewPager.offscreenPageLimit = mockJSON.length()-1
    viewPager.adapter = sectionsPagerAdapter
    //Use view pager to populate tabs
    val tabs: TabLayout = findViewById(R.id.tabs)
    tabs.setupWithViewPager(viewPager)
    val fab: FloatingActionButton = findViewById(R.id.fab)

    fab.setOnClickListener { view ->
        //TODO: Get data from all the fragments
        for(item in 0 until viewPager.childCount) {
            Log.d("BEAU", "${viewPager.getChildAt(item)}")
            //val child: View = viewPager.getChildAt(item)
        }
    }
}
}

SectionsPagerAdapter

class SectionsPagerAdapter(private val context: Context, fm: FragmentManager, private val mockJSON: JSONArray) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

override fun getItem(position: Int): Fragment {
    val jsonList = arrayListOf<Pair<String, ArrayList<String>>>()
    for(i in 0 until mockJSON.length()){
        @Suppress("unchecked_cast")
        jsonList.add(mockJSON[i] as Pair<String, ArrayList<String>>)
    }
    // getItem is called to instantiate the fragment for the given page.
    // Return a PlaceholderFragment (defined as a static inner class below)
    return PlaceholderFragment.newInstance(position + 1, jsonList[position].second)
}

override fun getPageTitle(position: Int): CharSequence? {
    //Sort through the JSON and get a list of sections.
    // Then, create tabs based on those sections
    val jsonList = arrayListOf<Pair<String, ArrayList<String>>>()
    for(i in 0 until mockJSON.length()){
        @Suppress("unchecked_cast")
        jsonList.add(mockJSON[i] as Pair<String, ArrayList<String>>)
    }
    val sectionArray = arrayListOf<String>()
    //Get the various sections from mockJSONArray
    for (arrayItem in jsonList) {
        sectionArray.add(arrayItem.first)
    }

    return sectionArray[position]
}

override fun getCount(): Int {
    //Sort through the JSON and get a list of sections.
    // Then, create tabs based on those sections
    val jsonList = arrayListOf<Pair<String, ArrayList<String>>>()
    for(i in 0 until mockJSON.length()){
        @Suppress("unchecked_cast")
        jsonList.add(mockJSON[i] as Pair<String, ArrayList<String>>)
    }

    val sectionArray = arrayListOf<String>()
    //Get the various sections from mockJSONArray
    for (arrayItem in jsonList) {
        sectionArray.add(arrayItem.first)
    }

    // Show total pages in relation to size of tab titles array.
    return sectionArray.size
}
}

PlaceholderFragment

class PlaceholderFragment : Fragment() {

private lateinit var pageViewModel: PageViewModel
private val viewIDArrayList = arrayListOf<Int>()
private lateinit var fragmentActivity: FragmentActivity

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    fragmentActivity = activity!!
    pageViewModel = ViewModelProviders.of(this).get(PageViewModel::class.java).apply {
        setIndex(arguments?.getInt(ARG_SECTION_NUMBER) ?: 1)
    }
}

override fun onDestroyView() {
    super.onDestroyView()
    Log.d("BEAU", "View Destroyed!")
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val root = inflater.inflate(R.layout.fragment_tab_test, container, false)
    val textView: TextView = root.findViewById(R.id.section_label)

    //Set page title with information gained from putInt in newInstance function below
    pageViewModel.text.observe(this, Observer<String> {
        textView.text = it
    })

    //get JSON data from bundle
    @Suppress("unchecked_cast")
    val jsonData = arguments!!.getStringArrayList("JSONData") as ArrayList<Pair<String, String>>

    fun createEditTextLine(titleString: String) {
        val verticalLayout = root.findViewById<LinearLayout>(R.id.createInspectionBodyLinearLayout)
        val layoutInflater = LayoutInflater.from(fragmentActivity)
        val linearLayout = layoutInflater.inflate(R.layout.template_edit_text, null) as LinearLayout
        verticalLayout.addView(linearLayout)
        val titleText = linearLayout.findViewById<TextView>(R.id.editTextTitle)
        titleText.id = ViewCompat.generateViewId()
        viewIDArrayList.add(titleText.id)
        titleText.text = titleString
        //if the number is even, color it differently to make the sections easier to differentiate
        if (titleText.id % 2 == 0) {
            linearLayout.setBackgroundColor(Color.parseColor("#F2F2F0"))
        }
    }

    fun createDatePickerLine(titleString: String) {
        val horizontalLayout = root.findViewById<LinearLayout>(R.id.createInspectionBodyLinearLayout)
        val layoutInflater = LayoutInflater.from(fragmentActivity)
        val linearLayout = layoutInflater.inflate(R.layout.template_date_picker, null) as LinearLayout
        horizontalLayout.addView(linearLayout)
        val dateTitle = linearLayout.findViewById<TextView>(R.id.dateTitle)
        dateTitle.id = ViewCompat.generateViewId()
        dateTitle.text = titleString
        viewIDArrayList.add(dateTitle.id)
        //if the number is even, color it differently to make the sections easier to differentiate
        if (dateTitle.id % 2 == 0) {
            linearLayout.setBackgroundColor(Color.parseColor("#F2F2F0"))
        }
    }

    fun createTimePickerLine(titleString: String) {
        val horizontalLayout = root.findViewById<LinearLayout>(R.id.createInspectionBodyLinearLayout)
        val layoutInflater = LayoutInflater.from(fragmentActivity)
        val linearLayout = layoutInflater.inflate(R.layout.template_time_picker, null) as LinearLayout
        horizontalLayout.addView(linearLayout)
        val timeTitle = linearLayout.findViewById<TextView>(R.id.timeTitle)
        timeTitle.id = ViewCompat.generateViewId()
        timeTitle.text = titleString
        viewIDArrayList.add(timeTitle.id)
        //if the number is even, color it differently to make the sections easier to differentiate
        if (timeTitle.id % 2 == 0) {
            linearLayout.setBackgroundColor(Color.parseColor("#F2F2F0"))
        }
    }

    fun createRatingBarLine(titleString: String) {
        val horizontalLayout = root.findViewById<LinearLayout>(R.id.createInspectionBodyLinearLayout)
        val layoutInflater = LayoutInflater.from(fragmentActivity)
        val linearLayout = layoutInflater.inflate(R.layout.template_rating_bar, null) as LinearLayout
        horizontalLayout.addView(linearLayout)
        val ratingTitle = linearLayout.findViewById<TextView>(R.id.ratingTitle)
        ratingTitle.id = ViewCompat.generateViewId()
        ratingTitle.text = titleString
        viewIDArrayList.add(ratingTitle.id)
        //if the number is even, color it differently to make the sections easier to differentiate
        if (ratingTitle.id % 2 == 0) {
            linearLayout.setBackgroundColor(Color.parseColor("#F2F2F0"))
        }
    }

    for (item in jsonData) {
        if (item.second == "editText") {
            createEditTextLine(item.first)
        }
        if (item.second == "datePicker") {
            createDatePickerLine(item.first)
        }
        if (item.second == "timePicker") {
            createTimePickerLine(item.first)
        }
        if (item.second == "rating") {
            createRatingBarLine(item.first)
        }
    }

    return root
}

companion object {
    /**
     * The fragment argument representing the section number for this
     * fragment.
     */
    private const val ARG_SECTION_NUMBER = "section_number"

    /**
     * Returns a new instance of this fragment for the given section
     * number.
     */
    @JvmStatic
    fun newInstance(sectionNumber: Int, jsonForThisSection: ArrayList<String>): PlaceholderFragment {
        return PlaceholderFragment().apply {
            arguments = Bundle().apply {
                putInt(ARG_SECTION_NUMBER, sectionNumber)
                putStringArrayList("JSONData", jsonForThisSection)
            }
        }
    }
}
}

PageViewModel

class PageViewModel : ViewModel() {

private val _index = MutableLiveData<Int>()
val text: LiveData<String> = Transformations.map(_index) {
    "Hello world from section: $it"
}

fun setIndex(index: Int) {
    _index.value = index
}
}

Any help would be very much appreciated.

Edit: working on attempting to use a shared ViewModel as suggested by @EpicPandaForce. If anyone has anymore specific ideas, please let me know.


Solution

  • Get the fragment from position, then call a fun getInputData()... function in order to retreive your data.. but since adapter.getItem(position) will re-instantiate your frag, you may have to put fragment references into a map, then, when you can retreive data based on position. ex:

    PlaceholderFragment class lets say we have a fun getInputData()....

    SectionPagerAdapter class:

    private val fragmentsRef = mutableMapOf<Int, PlaceholderFragment>()

    Update getItem:

       override fun getItem(position: Int): Fragment {
            ...
            val frag = PlaceholderFragment.newInstance(position + 1, jsonList[position].second)
    
            //Here we save a reference of the instantiated fragment
            fragmentsRef[position] = frag
            return frag
        }
    

    Then, remove fragment onDestroyItem:

    override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
                super.destroyItem(container, position, `object`)
                fragmentsRef.remove(position)
            }
    

    Call getFragmentAt when you need a fragment at a specific position:

    fun getFragmentAt(position: Int): PlaceholderFragment? = fragmentsRef[position]

    Finally, on floating action button click:

    fab.setOnClickListener { view ->
                for (item in 0 until viewPager.childCount) {
                    val frag = sectionsPagerAdapter.getFragmentAt(item)
                    val data = frag?.getInputData()
                    ...
                }
            }