I am considering a simple example consists of the following component:
MainActivity: has editText, button, viewPager
FragmentA: has nothing
FragmentB: has a textView
So, in the screen, you can swipe to see each fragment. I want my example to have the function: if one writes a text in the editText and click the button then it shows up in Fragment B.
A tried it by using notifyDatsetChanged() but it does not work. Could anyone help? Below is the whole code of mine:
MainActivity.kt
package com.myproject.chapterfivesectionone
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.myproject.chapterfivesectionone.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
lateinit var fragmentB: FragmentB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
val fragmentList = listOf(FragmentA(), FragmentB())
val adapter = FragmentAdapter(this)
adapter.fragmentList = fragmentList
binding.viewPager.adapter = adapter
binding.buttonSend.setOnClickListener {
var bundle = Bundle()
bundle.putString("key1", binding.editTextWriteSomething.text.toString())
fragmentB.arguments = bundle
adapter.notifyDataSetChanged()
}
}
}
FragmentAdapter.kt
package com.myproject.chapterfivesectionone
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
class FragmentAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
var fragmentList = listOf<Fragment>()
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return fragmentList.get(position)
}
}
FragmentA.kt
package com.myproject.chapterfivesectionone
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
class FragmentA : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_a, container, false)
}
}
FragmentB.kt
package com.myproject.chapterfivesectionone
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.myproject.chapterfivesectionone.databinding.FragmentBBinding
class FragmentB : Fragment() {
lateinit var binding: FragmentBBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentBBinding.inflate(inflater, container, false)
if (arguments?.getString("key1") != null) {
binding.textViewInFragmentB.text = arguments?.getString("key1")
}
return binding.root
}
}
Currently, fragmentB is inside fragmentList so you can retrieve it like fragmentList[fragmentBPosition]. Then defind a function inside FragmentB for update the TextView
binding.buttonSend.setOnClickListener {
(fragmentList[1] as FragmentB).updateTheTextView("new")
// if you want code more clean, can replace hard code value by a constant or you can write some logic to find FragmentB from fragmentList
}
FragmentB
class FragmentB : Fragment() {
fun updateTheTextView(newText : String) {
binding.textViewInFragmentB.text = newText
}
}
Related
I have 2 fragments, first(start_fragment) should shows when app starts and have 2 buttons, 1 which close app(declineButton), and second which hide this fragment and show main fragment(webview_fragment). If user press agreeButton, next time app will starts already in main fragment(webview_fragment). How could i save that user press agree button? I tried to create boolean variable but can't imagine how can i put new value from button within fragment to variable within mainactivity.
MainActivity.kt
package com.example.webviewapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
var firstlauch: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (firstlauch == true) {
supportFragmentManager
.beginTransaction()
.replace(R.id.fullcreen_holder, start_fragment())
.commit()
} else {
supportFragmentManager
.beginTransaction()
.replace(R.id.webview_holder, webview_fragment())
}
}
}
start_fragment.kt
package com.example.webviewapp
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
class start_fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.layout_start_fragment, container, false)
val agreeButton: Button = view.findViewById(R.id.privacy_agree)
val declineButton: Button = view.findViewById(R.id.privacy_decline)
agreeButton.setOnClickListener {
val fragment = webview_fragment()
val transaction = fragmentManager?.beginTransaction()
transaction?.remove(this)?.replace(R.id.webview_holder, fragment)?.commit()
}
declineButton.setOnClickListener {
requireActivity().finishAndRemoveTask()
}
return view
}
}
You can use sharedPreferences for keep small data.
In MainActivity
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
val firstLaunch = sharedPref.getBoolean("YOUR_KEY", true)
In Fragment
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
val editor = sharedPref.edit()
agreeButton.setOnClickListener {
editor.putBoolean("YOUR_KEY", true)
val fragment = webview_fragment()
val transaction = fragmentManager?.beginTransaction()
transaction?.remove(this)?.replace(R.id.webview_holder, fragment)?.commit()
}
declineButton.setOnClickListener {
editor.putBoolean("YOUR_KEY", false)
requireActivity().finishAndRemoveTask()
}
I'm new to kotlin and android development.
I'm currently learning fragment and how to create it, receive data from one fragment and display it on another through the activity that connects them.
What I have done:
I created two fragment
I called them in the main activity dynamically
I used viewBinding
I created an interface called fragmentAactionListener in the SendingTextFragment and it has a method that will be defined in the main activity but called from the SendingTextFragment
What I was hoping to achieve:
When someone type a name in the editView and click the send button, it will call the method defined in the main activity and it will pass it to the DisplayFragment.
But when I type a name and click the button but the application class and say kotlin.UninitializedPropertyAccessException: lateinit property binding has not been initialized but initialized it.
The error message:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.fragmenttest, PID: 5187
kotlin.UninitializedPropertyAccessException: lateinit property binding has not been initialized
at com.example.fragmenttest.DisplayFragment.recieveName(DisplayFragment.kt:31)
at com.example.fragmenttest.MainActivity.onButtonClick(MainActivity.kt:37)
at com.example.fragmenttest.SendingTestFragment.onButtonClicked(SendingTestFragment.kt:52)
at com.example.fragmenttest.SendingTestFragment.onViewCreated$lambda-0(SendingTestFragment.kt:46)
at com.example.fragmenttest.SendingTestFragment.lambda$g_0sTs_brBZ0HIBdlZHmgd8vecI(Unknown Source:0)
at com.example.fragmenttest.-$$Lambda$SendingTestFragment$g_0sTs_brBZ0HIBdlZHmgd8vecI.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7448)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
My code:
The MainActivity
package com.example.fragmenttest
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.example.fragmenttest.databinding.ActivityMainBinding
class MainActivity : FragmentActivity(), SendingTextFragment.FragmentActionListener {
private lateinit var binding : ActivityMainBinding
private var displayFragment : DisplayFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
var sendingTestFragment = SendingTextFragment()
displayFragment = DisplayFragment()
addFragment(sendingTestFragment)
}
// add fragment
fun addFragment(fragment: Fragment){
// get the fragment manager and start fragment transaction
var fTransaction = supportFragmentManager.beginTransaction()
fTransaction.replace(R.id.FLFragment, fragment)
fTransaction.addToBackStack(null)
fTransaction.commit()
}
override fun onButtonClick(name: String) {
displayFragment!!.recieveName(name)
addFragment(displayFragment!!)
}
}
The SendingFragment
package com.example.fragmenttest
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.fragmenttest.databinding.FragmentSendingTestBinding
import java.text.SimpleDateFormat
class SendingTextFragment : Fragment() {
private lateinit var binding : FragmentSendingTestBinding
private var activityContext : SendingTextFragment.FragmentActionListener? = null
private var simpleDateFormat: SimpleDateFormat? = null
interface FragmentActionListener{
fun onButtonClick(name : String)
}
override fun onAttach(context: Context) {
super.onAttach(context)
activityContext = context as FragmentActionListener
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentSendingTestBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
simpleDateFormat = SimpleDateFormat("HH:mm:ss")
binding.time.text = simpleDateFormat?.format(System.currentTimeMillis())
binding.button.setOnClickListener {
onButtonClicked(it)
}
}
private fun onButtonClicked(view: View){
if (activityContext != null){
activityContext?.onButtonClick(binding.name.text.toString())
}
}
}
The Display
package com.example.fragmenttest
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.example.fragmenttest.databinding.FragmentDisplayBinding
class DisplayFragment : Fragment() {
private lateinit var binding : FragmentDisplayBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentDisplayBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
fun recieveName(name : String){
binding.name.text = name
}
}
I'm new to Kotlin and Fragments, actually in my App i have two fragments, Fragment1 and Fragment2
In the first fragment i have a method which get data from some editTexts and add them to a MutableList then in my Fragment2 i have a ListView in which i would be able to show that MutableList...
But how can i pass the MutableList from Fragment1 to Fragment2?
Here is my Fragment1 code:
private var listArticoli: MutableList<Articolo>? = null
private fun addBarcode(barcode: String, qta: String) {
if (barcode.isEmpty()) {
txtBarcode.requestFocus()
return;
}
listArticoli?.add(Articolo(barcode, qta.toInt()))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
txtBarcode = view.findViewById(R.id.txtBarcode)
listArticoli = mutableListOf()
btnArticoli.setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) // button on click of which i navigate to Fragment2
}
}
Fragment2 code:
class SecondFragment : Fragment() {
lateinit var listView: ListView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_second, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listView = view.findViewById(R.id.listView)
view.findViewById<Button>(R.id.btnIndietro).setOnClickListener {
findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
}
}
}
1.Share the viewmodel with their common parent activity
2.FragmentResultListener from fragment(after 1.3.0-alpha04)
To access Mutable list in both the fragments,you can create ViewModel which consists of MutableLiveDate of you required data type.Then expose MutableLiveData from viewmodel.So use the same ViewModel in both the fragments by setting data from one fragment1 and by observing data from fragment2.
SampleViewModel.kt
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class SampleViewModel: ViewModel() {
var mutableLiveData:MutableLiveData<MutableList<Articolo> = MutableLiveData()
}
Fragment1.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
class Fragment1 : Fragment() {
private lateinit var viewModel: SampleViewModel
var arrayList: ArrayList<Articolo> = ArrayList()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(SampleViewModel::class.java)
//TODO: Add data to arraylist
viewModel.mutableLiveData.value = arrayList
}
}
Fragment2.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
class Fragment2 : Fragment() {
private lateinit var viewModel: SampleViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(SampleViewModel::class.java)
viewModel.mutableLiveData.observe(viewLifecycleOwner, Observer {
//TODO:Can do you requirements here
})
}
}
I was following the tutorial here: https://gist.github.com/EmmanuelGuther/1fde5cfbd1cdcd21cd852e3bb5716e02.
I have one button which computes the total from user input. It uses the layout fragment_ab.xml. I would like to add a "Clear" button but I do not know how to add a second button.
In the main activity I would just use clear.setOnClickListener{} alongside compute.setOnClickListener{} but it doesn't work here. I'm new to fragments.
Here's the code:
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.fragment.app.Fragment
class AbActivity: Fragment(), View.OnClickListener {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view: View = inflater!!.inflate(R.layout.fragment_ab, container, false)
val btn: Button = view.findViewById(R.id.compute)
btn.setOnClickListener(this)
return view
}
companion object {
fun newInstance(): AbActivity{
return AbActivity()
}
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.compute -> {
Toast.makeText(
activity,"Yay!",
Toast.LENGTH_SHORT).show()
}
else -> {
}
}
}
}
Add a button in you fragment_ab.xml with id = clear and then do as following:
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.fragment.app.Fragment
class AbActivity: Fragment(), View.OnClickListener {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view: View = inflater!!.inflate(R.layout.fragment_ab, container, false)
val btn: Button = view.findViewById(R.id.compute)
btn.setOnClickListener(this)
// for clear button
val btnClear: Button = view.findViewById(R.id.clear)
btnClear.setOnClickListener(this)
return view
}
companion object {
fun newInstance(): AbActivity{
return AbActivity()
}
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.compute -> {
Toast.makeText(
activity,"Yay!",
Toast.LENGTH_SHORT).show()
}
// clear button click
R.id.clear -> {
Toast.makeText(
activity,"Clear!",
Toast.LENGTH_SHORT).show()
}
else -> {
}
}
}
I am passing arguments using safeargs. In the reciving fragment I am getting a compile error: 'Required Bundle Found Bundle?'. Cannot see where the error has crept in.
Googled around, checked text and udacity tutorial
Where error appears (at 'arguments')
package com.example.android.naveditoryoutube
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_two_fragment.*
class FragmentTwo : Fragment() {
companion object {
fun newInstance() = FragmentTwo()
}
private lateinit var viewModel: FragmentTwoViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_two_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(FragmentTwoViewModel::class.java)
}
override fun onStart() {
super.onStart()
var args = FragmentTwoArgs.fromBundle(arguments)
argText.text = args.calculated_Number
}
}
Sending code:
package com.example.android.naveditoryoutube
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.navigation.Navigation
import kotlinx.android.synthetic.main.fragment_one_fragment.*
class FragmentOne : Fragment() {
companion object {
fun newInstance() = FragmentOne()
}
private lateinit var viewModel: FragmentOneViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_one_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(FragmentOneViewModel::class.java)
var calculated_Number : String = viewModel.sendNewNumber().toString()
button_calculate.setOnClickListener{view: View ->
if (number_box.text.isNotEmpty()){
var number_entered: String = number_box.text.toString()
viewModel.findNewNumber((number_entered))
calculated_Number = viewModel.sendNewNumber()
Navigation.findNavController(view).navigate(FragmentOneDirections.actionFragmentOneToFragmentTwo(calculated_Number))
}
}
}
}
Error appears here
var args = FragmentTwoArgs.fromBundle(arguments)
arguments is expected to be Bundle but is Bundle?
I had to change it to use !!
var args = FragmentTwoArgs.fromBundle(arguments!!)
use requireArguments() instead of arguments!!
try using requireArguments() instead of arguments
var args = FragmentTwoArgs.fromBundle(requireArguments())
You shouldn't use FragmentTwoArgs.fromBundle(arguments)
just use:
val args: FragmentTwoArgsby navArgs()
And in your method call:
val amount = args.amount
It's work for me, see official guide:
https://developer.android.com/topic/libraries/architecture/navigation/navigation-pass-data
Instead of
var args = FragmentTwoArgs.fromBundle(arguments)
argText.text = args.calculated_Number
you can use
arguments?.let {
val args = FragmentTwoArgs.fromBundle(it)
argText.text = args.calculated_Number
}