Send custom class object via Bundle between fragments with Parcebale - android

I am using fragments inside my main activity and I want to send an object of my custom class "TaskWithUserAndProfile" to the TaskDetailsFragment
I found out that you can do it with Bundle and made it send a string, but things got complicated when I tried to send with Parcebale.
here are some parts of my code for better understanding:
TaskWithUserAndProfile.kt
class TaskWithUserAndProfile() : Parcelable{
override fun writeToParcel(p0: Parcel?, p1: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
var profile = Profile()
var task = Task()
var user = User()
constructor(parcel: Parcel) : this() {
//profile = parcel.read
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TaskWithUserAndProfile> {
override fun createFromParcel(parcel: Parcel): TaskWithUserAndProfile {
return TaskWithUserAndProfile(parcel)
}
override fun newArray(size: Int): Array<TaskWithUserAndProfile?> {
return arrayOfNulls(size)
}
}
}
HomeFragment.kt
//Inside onCreateView
adapter = TasksAdapter(tasksArray) { item ->
println(item.profile)
val bundle = Bundle()
bundle.putParcelable("MyItem", item)
val taskDetailsFragment = TaskDetailsFragment()
taskDetailsFragment.arguments = bundle
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.container, taskDetailsFragment)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
}
How should my class that implements the Parcebale look like and how can I then send and receive the item object in fragments?

You don't need to use Parcelable even, just simply define an TaskWithUserAndProfile variable in your TaskDetailsFragment and set in in HomeFragment.
TaskDetailsFragment.kt
class TaskDetailsFragment : Fragment() {
var selectedTask: TaskWithUserAndProfile? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
}
}
HomeFragment.kt
//Inside onCreateView
adapter = TasksAdapter(tasksArray) { item ->
val taskDetailsFragment = TaskDetailsFragment()
taskDetailsFragment.selectedTask = item
val fragmentTransaction =
fragmentManager?.beginTransaction()
fragmentTransaction?.replace(R.id.container, taskDetailsFragment)
fragmentTransaction?.addToBackStack(null)
fragmentTransaction?.commit()
}

if you wanna keep using parcelize, just try this sample:
#Parcelize
data class TaskWithUserAndProfile(var profile:Profile, var task :Task, var user:User) : Parcelable{}
I could miss something from your class but the idea should looks like this, so use annotation #Parcelize and Parcelable implementation (do not need to override any method).
Update
Thanks for reminder. You will have to add this to your gradle file:
androidExtensions {
experimental = true
}

Use this plugin:
android-parcelable-intellij-plugin-kotlin
for TaskWithUserAndProfile, Profile, Task, User models.

Related

Is there any simple solutions to pass non-empty Array to another class?

Get numbers
class Base : Fragment() {
val time = ArrayList<Double>()
val amplitude = ArrayList<Double>()
var flag = 0
private fun readNumbersFromCSV(fileName: String) {
val textView: TextView = requireView().findViewById(R.id.result)
val timeTextView: TextView = requireView().findViewById(R.id.Time)
val amplitudeTextView: TextView = requireView().findViewById(R.id.Amplitude)
timeTextView.movementMethod = ScrollingMovementMethod()
amplitudeTextView.movementMethod = ScrollingMovementMethod()
try {
timeTextView.append("Time, s\n")
amplitudeTextView.append("Amplitude\n")
val file = File(fileName)
if(!file.exists()){
throw FileNotFoundException("File not found")
}
val reader = BufferedReader(FileReader(file))
var line = reader.readLine()
while (line != null) {
val parts = line.split(",")
if (parts.size == 2) {
time.add(parts[1].toDouble())
amplitude.add(parts[0].toDouble())
timeTextView.append(parts[1] + "\n")
amplitudeTextView.append(parts[0] + "\n")
}
line = reader.readLine()
}
flag = 1
reader.close()
} catch (e: FileNotFoundException) {
textView.text = "Error: File Not Found"
} catch (e: Exception) {
textView.text = "Error: ${e.message}"
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_base, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()
val file = File(path, "data.csv").toString()
readNumbersFromCSV(file)
/*now im ready to pass data to another class*/
}
}
Do some calculations on those numbers
class Calculations : Fragment() {
private fun meanAmplitude(amplitudes: List<Double>): Double {
if(amplitudes.isEmpty()) return 3.5
return amplitudes.sum() / amplitudes.size
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_calculations, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val copiedList = Base().amplitude.toList() /* data from file passed to new array*/
val textViewAmp: TextView = view.findViewById(R.id.Camplitude)
val valueOfMean = meanAmplitude(copiedList).toString() /*calculate mean value*/
textViewAmp.text = valueOfMean /*display it*/
}
}
MyAdapter
internal class MyAdapter (var context: Context, fm: FragmentManager, var totalTabs: Int): FragmentPagerAdapter(fm) {
override fun getCount(): Int {
return totalTabs
}
override fun getItem(position: Int): Fragment {
return when(position){
0 -> {
Base()
}
1 -> {
Calculations()
}
2 -> {
About()
}
else -> getItem(position)
}
}
}
HomeActivity
class HomeActivity : AppCompatActivity() {
private lateinit var tabLayout: TabLayout
private lateinit var viewPager: ViewPager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
supportActionBar?.hide()
setContentView(R.layout.activity_home)
tabLayout = findViewById(R.id.tabLayout)
viewPager = findViewById(R.id.viewPager)
tabLayout.addTab(tabLayout.newTab().setText("Data"))
tabLayout.addTab(tabLayout.newTab().setText("Calculations"))
tabLayout.addTab(tabLayout.newTab().setText("About"))
tabLayout.tabGravity = TabLayout.GRAVITY_FILL
val adapter = MyAdapter(this, supportFragmentManager, tabLayout.tabCount)
viewPager.adapter = adapter
viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout))
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
viewPager.currentItem = tab!!.position
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
}
}
Im new in Kotlin. I have a problem with initializing an array that is being filled with data from a .csv file in the Base class, and then its contents should be passed to the Calculations class. The problem is that the array instance is being passed before it is being filled with numbers. Two fragments are generated probably in the same time.
Loading from file and initializing an array in the first class works, elements are displayed on the screen without any problems. After passing the array to the second class, it is empty.
I tried to do a flag, but it doesnt work like I though. Im not using activities, just Fragments and ViewPager. I tried Bundles but its hard to apply new things in my messy project.
Here:
val copiedList = Base().amplitude.toList()
You are instantiating a new instance of Base by calling its constructor. This new instance shares nothing with any previous instance. It's a brand new Base that hasn't done anything yet so its lists are still empty.
To pass data between fragments, you should create an arguments Bundle and pass that to the new fragment. The reason you need to do it this way is that Android automatically destroys and recreates Fragment instances under various conditions, and only the arguments data is preserved for the new instance.
The conventional way to do this is to define a Fragment factory function named newInstance() in its companion object. Then the Fragment can unpack the new data in onViewCreated(). You have to convert to and from DoubleArrays because Bundle doesn't support Lists.
class Calculations private constructor(): Fragment(R.layout.fragment_calculations) {
companion object {
private const val TIME_LIST_KEY = "timeList"
private const val AMP_LIST_KEY = "ampList"
fun newInstance(timeList: List<Double>, ampList: List<Double>) =
Calculations().apply {
arguments = bundleOf(
TIME_LIST_KEY to timeList.toDoubleArray(),
AMP_LIST_KEY to ampList.toDoubleArray()
)
}
}
private fun meanAmplitude(amplitudes: List<Double>): Double {
if(amplitudes.isEmpty()) return 3.5
return amplitudes.sum() / amplitudes.size
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val timeList = requireArguments().getDoubleArray(TIME_LIST_KEY).toList()
val ampList = requireArguments().getDoubleArray(AMP_LIST_KEY).toList()
val textViewAmp: TextView = view.findViewById(R.id.Camplitude)
val valueOfMean = meanAmplitude(ampList).toString() /*calculate mean value*/
textViewAmp.text = valueOfMean /*display it*/
}
}
Then in your first fragment, you use Calculations.newInstance() to create your second fragment before passing it to the transaction manager.
By the way, there's a major bug in your Base class. Since Fragment instances can be reused by the OS, the same fragment can go through multiple lifecycles. Since you are adding your data to the same ArrayLists every time onViewCreated() is called, they will get longer and longer as the user rotates the screen or navigates back and forth in the app. You should either remove those properties and use local variables instead, or you should clear those ArrayLists in onDestroyView().

ViewModelProvider.Factory always return one viewmodel

I have a TabLayout, contains three Fragment (Created by same instance) by SectionsPagerAdapter. Inside the fragment, I try with ViewModelProvider.Factory to create independent viewmodel, however, I found all fragments always update content together with same data.
I have debugged and found it always return the same viewmodel even
with difference BillType, and
something's weird that when enterence into activity, the
Factory.create is only invoked once.
// Log
D/BillType: OUTCOME
D/Factory crate BillType: OUTCOME
D/ViewModel Init BillType: OUTCOME
D/viewModel Bill:BillType: OUTCOME
D/BillType: INCOME
D/viewModel Bill:BillType: OUTCOME
D/BillType: TRANSFER
D/viewModel Bill:BillType: OUTCOME
I cannot figure out where is wrong, same code runs correctly before.
class BillViewModel(billType: BillType): ViewModel() {
val bill: MutableLiveData<Bill> = MutableLiveData()
init {
Log.d("ViewModel Init BillType", billType.toString())
bill.value = Bill.QBill().apply {
type = billType
}
}
class NewBillViewModelFactory(val billType: BillType): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
Log.d("Factory crate BillType", billType.toString())
return modelClass.getConstructor(BillType::class.java)
.newInstance(billType)
}
}
}
enum class BillType(val type: Int) {
OUTCOME(0),
INCOME(1),
TRANSFER(2);
}
class NewBillFragment: BaseFragment() {
...
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
billType = BillType.values()[arguments?.getInt(BILLTYPE, 0) ?: 0]
Log.d("BillType", billType.toString())
viewModel = ViewModelProvider(requireActivity(), NewBillViewModel.NewBillViewModelFactory(billType))[NewBillViewModel::class.java]
Log.d("viewModel Bill:BillType", viewModel.bill.value?.type.toString())
_binding = FragmentBillNewBinding.inflate(layoutInflater, container, false)
with(binding) {
data = viewModel
lifecycleOwner = activity
... ui ...
return binding.root
}
companion object {
private const val BILLTYPE = "billtype"
#JvmStatic
fun newInstance(billType: Int): NewBillFragment {
return NewBillFragment().apply {
arguments = Bundle().apply {
putInt(BILLTYPE, billType)
}
}
}
}
}
class SectionsPagerAdapter(private val context: Context, fm: FragmentManager)
: FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment = BillFragment.newInstance(position)
override fun getPageTitle(position: Int): CharSequence = context.resources.getString(TAB_TITLES[position])
override fun getCount(): Int = 3
}
Because you are creating a Shared ViewModel with requireActivity() . So it will return ViewModel with reference to Activity not Fragment.
If you want to keep ViewModel Fragment scoped Then you should pass Fragment as ViewModelStoreOwner .
viewModel = ViewModelProvider(this, NewBillViewModel.NewBillViewModelFactory(billType))[NewBillViewModel::class.java]

Kotlin: spinner onItemSelectedListener from another fragment

i have a fragment with a BottomNavigationView, a Spinner and a FrameLayout, in the FrameLayout appears a a new fragment with the BottomNavigationView.setOnNavigationItemSelectedListener, this is my code:
Fragment ValcuotaEvolFragment
class ValcuotaEvolFragment: Fragment() {
lateinit var fragment : Fragment
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?, savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_valcuota_evol, container, false)
val menuBottom: BottomNavigationView = root.findViewById(R.id.nav_view_valcuota_evol)
val spn : Spinner = root.findViewById(R.id.spnAFP)
val db = DataBaseHandler(activity!!.applicationContext)
val afpListName : ArrayList<String> = db.getAFPNames()
fragment= ValcuotaChartFragment()
val bundle = Bundle()
spn.adapter= ArrayAdapter<String>(activity!!.applicationContext,android.R.layout.simple_spinner_dropdown_item,afpListName)
spn.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
bundle.putString("afp",spn.selectedItem.toString())
}
override fun onNothingSelected(parent: AdapterView<*>) { }
}
menuBottom.setOnNavigationItemSelectedListener {
menuItem ->
when(menuItem.itemId){
R.id.nav_evolcuota_chart -> {
fragment = ValcuotaChartFragment()
}
R.id.nav_evolcuota_data -> {
fragment = ValcuotaDataFragment()
}
}
fragment.setArguments(bundle);
val transaction = childFragmentManager.beginTransaction()
transaction.replace(R.id.frame_valcuota_evol, fragment)
transaction.addToBackStack(null)
transaction.commit()
true
}
fragment.setArguments(bundle);
val transaction = childFragmentManager.beginTransaction()
transaction.replace(R.id.frame_valcuota_evol, fragment)
transaction.addToBackStack(null)
transaction.commit()
return root
}
}
I pass to the new fragment the value "afp" through a Bundle, now i need the new fragment to do something different depending on what I select in the spinner of ValcuotaEvolFragment
this is what i need:
class ValcuotaDataFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_valcuota_data, container, false)
val afp = arguments!!.getString("afp")
if(afp == "something"){
doSomething()
else {
doSomethingElse
}
return root
}
}
this actually works, but only when i change the item in the BottomNavigationView i need this works when change the item in the Spinner, thx
EDIT
The EventBus solution works fine , but now i have a new problem in ValcuotaDataFragment i have a RecyclerView, so now i need fill the RecyclerView after change the item in the Spinner, this is how i do it now:
val rcViewValcuota = root. findViewById(R.id.rc_valcuota_data) as RecyclerView
var valcuota : MutableList<ValcuotaModel>
val db = DataBaseHandler(activity!!.applicationContext)
valcuota = db.getCompleteValCuota(spinnerData.selectedItem,"desc")
rcViewValcuota.adapter= ContentValcuotaMonthlyAdapter(valcuota)
i can't access the "root" from the function listenItemChange
Okay, so when you're selecting a different item from the spinner, your fragment is not aware of it unless you pass data to fragment. So for informing the fragment, you can implement the interface if you'd like. Or my favorite you can use EventBus library to pass the data.
I'll show you how you can implement EventBus.
First, add EventBus Dependency is the Gradle file:
implementation 'org.greenrobot:eventbus:3.1.1'
Okay now create a data class for passing data say SpinnerData:
data class SpinnerData(val selectedItem:String)
Then inside your itemSelected listener, pass the data using EventBus:
spn.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
// bundle.putString("afp",spn.selectedItem.toString())
//insted add following line
EventBus.getDefault().post(SpinnerData(spn.selectedItem.toString()))
}
override fun onNothingSelected(parent: AdapterView<*>) { }
}
Then inside your ValcuotaDataFragment add the following:
#Subscribe
fun listenItemChange(spinnerData: SpinnerData){
if (spinnerData.selectedItem == "something") {
doSomething()
} else {
doSomethingElse()
}
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
Now, whenever you change the spinner item Evenbus will be triggered and pass the data to the Subscribed method inside your fragment.
Hope this helps, let me know if you get stuck somewhere.
Edit:
This won't work if your fragment isn't initialized already.
SO keep your line inside your itemSelected listener for first time use:
bundle.putString("afp",spn.selectedItem.toString())

How to use the FragmentPagerAdapter with unique unrelated fragments?

I am trying to implement a simple swiping action between the three main screens of my app (feed, forum & profile).
I've been looking for guides online but they all seem to take the same approach and show how to implement the adapter using fragments that are simple instances of a basic class that for example just inflates a title and a number on the screen.
In my case, the fragments are not all instances of the same class and are completely different.
M problem lays in the FragmentPagerAdapter. I am not sure how to return each fragment in the getItem method.
I have tried the following but it doesn't except it as a valid return statement and is still expecting one:
class PagesPagerAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {
override fun getItem(p0: Int): Fragment? {
return when (p0){
0 -> FeedFragment.newInstance()
1 -> BoardFragment.newInstance()
2 -> ProfileFragment.newInstance()
else -> FeedFragment.newInstance()
}
}
override fun getCount(): Int {
return 3
}
}
This is an example of one of my fragments:
class FeedFragment : Fragment() {
val galleryAdapter = GroupAdapter<ViewHolder>()
private fun setUpGalleryAdapter() {
feed_gallary.adapter = galleryAdapter
val galleryLayoutManager = GridLayoutManager(this.context, 3)
feed_gallary.layoutManager = galleryLayoutManager
val dummyUri =
"https://firebasestorage.googleapis.com/v0/b/dere-3d530.appspot.com/o/20150923_100950.jpg?alt=media&token=97f4b02c-75d9-4d5d-bc86-a3ffaa3a0011"
val imageUri = Uri.parse(dummyUri)
if (imageUri != null) {
galleryAdapter.add(FeedImage(imageUri))
galleryAdapter.add(FeedImage(imageUri))
galleryAdapter.add(FeedImage(imageUri))
galleryAdapter.add(FeedImage(imageUri))
galleryAdapter.add(FeedImage(imageUri))
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setUpGalleryAdapter()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_feed, container, false)
companion object {
fun newInstance(): FeedFragment = FeedFragment()
}
}
And this is my main activity:
class MainActivity : AppCompatActivity() {
private lateinit var viewPager: ViewPager
private lateinit var pagerAdapter: PagesPagerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager = findViewById<ViewPager>(R.id.view_pager)
pagerAdapter = PagesPagerAdapter(supportFragmentManager)
viewPager.adapter = pagerAdapter
}
}
I have also tried to define a variable currentFragment, have it reassigned for each of the when statements and have it be the return value, but as all my fragments are unique I am getting a type mismatch when trying to assign them all to one variable.
Is there some Array I can store the fragments in and then return the array position?
I feel like there is probably a simple elegant and common solution here.
Without seeing all of your code I cannot be sure but one thing to note is you must return an instance of your fragment not a reference to the class. Ex: FeedFragment() instead of FeedFragment.
Also, your when statement does not have a default case resulting in a compile error for the getItem method as you have a code path that doesn't return anything. Try the following code for getItem
override fun getItem(p0: Int): Fragment {
when(p0){
0 -> return FeedFragment.newInstance()
1 -> return BoardFragment.newInstance()
else -> return ProfileFragment.newInstance()
}
}
Unfortunately, there is no elegant solution for this case. You can store created fragment in array and then use this array when you need to access related fragment instance.
class PagesPagerAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {
private val fragments = SparseArray<WeakReference<Fragment>>()
override fun getCount() = 3
override fun getItem(position: Int): Fragment? {
val fragmentReference = fragments[position]
return if (fragmentReference?.get() != null) {
fragmentReference.get()
} else {
return when (position) {
0 -> FeedFragment.newInstance()
1 -> BoardFragment.newInstance()
2 -> ProfileFragment.newInstance()
else -> error("Incorrect position: $position")
}
}
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as Fragment
fragments.put(position, WeakReference(fragment))
return fragment
}

Passing a value from Activity to Fragment in Kotlin

I created a bottom navigation activity in my project, which contains one activity and two fragments. In Main Activity I have value stored in a variable but if I pass the value to the fragments then I am getting NullPointer Exception error. I am using kotlin in my project and any help is appreciated.
Expectation
Get Value into Fragment from MainActivity. MainActivity--->TestOneFragment
Language Used
Kotlin
Main Activity
class Test : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener
{
private val KEY_POSITION = "keyPosition"
private var navPosition: BottomNavigationPosition = BottomNavigationPosition.ONE
private lateinit var toolbar: Toolbar
private lateinit var bottomNavigation: BottomNavigationView
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
restoreSaveInstanceState(savedInstanceState)
setContentView(R.layout.activity_test)
toolbar = findViewById(R.id.toolbar)
bottomNavigation = findViewById(R.id.bottom_navigation)
setSupportActionBar(toolbar)
initBottomNavigation()
initFragment(savedInstanceState)
var Name:String=intent.getStringExtra("name")
println("Test CLLicked: $Name")
//This code is to pass the value to Fragment
var bundle=Bundle()
bundle.putString("name",Name)
var frag=TestFragment()
frag.arguments=bundle
}
override fun onSaveInstanceState(outState: Bundle?)
{
outState?.putInt(KEY_POSITION, navPosition.id)
super.onSaveInstanceState(outState)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean
{
navPosition = findNavigationPositionById(item.itemId)
return switchFragment(navPosition)
}
private fun restoreSaveInstanceState(savedInstanceState: Bundle?)
{
savedInstanceState?.also {
val id = it.getInt(KEY_POSITION, BottomNavigationPosition.ONE.id)
navPosition = findNavigationPositionById(id)
}
}
private fun initBottomNavigation()
{
bottomNavigation.active(navPosition.position)
bottomNavigation.setOnNavigationItemSelectedListener(this)
}
private fun initFragment(savedInstanceState: Bundle?)
{
savedInstanceState ?: switchFragment(BottomNavigationPosition.ONE)
private fun switchFragment(navPosition: BottomNavigationPosition): Boolean {
return supportFragmentManager.findFragment(navPosition).let {
if (it.isAdded) return false
supportFragmentManager.detach() // Extension function
supportFragmentManager.attach(it, navPosition.getTag()) // Extension function
supportFragmentManager.executePendingTransactions()
}
}
private fun FragmentManager.findFragment(position: BottomNavigationPosition): Fragment
{
return findFragmentByTag(position.getTag()) ?: position.createFragment()
}
}
TestOneFragment
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
val testName= arguments!!.getString("name")
....
}
Error
kotlin.KotlinNullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
Here is an example of the newInstance pattern for creating Fragments.
This is within a companion object, which is pretty much just a way to say "these things are Static."
First, you should define constants for your Bundle names, this will help keep everything aligned. Next, define a newInstance method that takes your parameters, such as the name.
And within there, you will create your Fragment and return it. This way, your Activity doesn't have to worry about the Bundle or anything. All your logic is within one place, for storing/retrieving, all within your Fragment.
class TestOneFragment {
companion object {
const val ARG_NAME = "name"
fun newInstance(name: String): TestOneFragment {
val fragment = TestOneFragment()
val bundle = Bundle().apply {
putString(ARG_NAME, name)
}
fragment.arguments = bundle
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val name = arguments?.getString(ARG_NAME)
// ...
}
}
And now, you can easily get your Fragment by doing the following.
class Test : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
// ...
val name = intent.getStringExtra("name")
// Creating the new Fragment with the name passed in.
val fragment = TestFragment.newInstance(name)
}
}
Hopefully that helps!

Categories

Resources