Reputation: 405
I have a TabLayout
with 3 fragments inside. Fragment 0 contains details, 1 is a description and 2 is location.
But no matter how I try to call the fragment Ids inside my carDetailsActivity
or I try to pass the value from the activity to the fragments it keeps calling on null and giving the following result:
Tab Fragment
Now I tried multiple ways one of them to Bundle
the text value from the activity to the fragment but I still got null "IllegaleStateException". (which was the best option to my knowledge) but it still didnt work.
This is how I want it to look (the design is in arabic):
Here is my Activity where im calling them at the moment but I just get empty strings (had to give them ? because the app kept crashing if I didnt):
class CarDetailsActivity : BaseActivity() {
val TAG = "CarDetailsActivity"
override fun onCreate(savedInstanceState: Bundle?) {
val car = intent.extras!!.getParcelable<Car>(DETAILS_TRANSFER) as Car
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_car_details)
activateToolbar(true)
carPriceDetails?.text = car.price.toString()
carDate?.text = car.date
carCategory?.text = car.category
carModel?.text = car.brandModel
carYear?.text = car.modelYear
carKilometer?.text = car.kilometer.toString()
carGear?.text = car.gearType
carFuel?.text = car.fuelType
val fragmentAdapter = FragmentCarInfo(supportFragmentManager)
viewPager.adapter = fragmentAdapter
tabLayout.setupWithViewPager(viewPager)
carBrand.text = car.brand
carModel.text = car.brandModel
carYear.text = car.modelYear
Picasso.with(this).load(car.image)
.error(R.drawable.ic_cars)
.placeholder(R.drawable.ic_cars)
.into(carImage)
}
}
Here is the main Fragment
that contains that layout that contains the details:
class InfoFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_car_details_info, container, false)
}
}
I have asked this similar question before but it was'nt clear enough so I hope this was a better way to post my problem.
-EDIT My tabLayout viewPager uses this
class FragmentCarInfo (fm : FragmentManager) : FragmentPagerAdapter(fm){
override fun getItem(position: Int): Fragment {
return when (position) {
0-> {
InfoFragment()
}
1 -> {
AboutFragment()
}
else -> {
return LocationFragment()
}
}
}
override fun getCount(): Int {
return 3
}
override fun getPageTitle(position: Int): CharSequence? {
return when (position) {
0-> "Details"
1-> "About"
else -> {
return "Locations"
}
}
}
}
Upvotes: 0
Views: 154
Reputation: 812
First of all, I would recommend ditching the old and nasty TabLayout and switch to the new ViewPager2 instead of the old ViewPager. UI wise, instead of the TabLayout, create custom 3 buttons inside a LinearLayout or ConstraintLayout.
Now, in your case I would use the ViewPager2 with a FragmentStateAdapter because it was designed to work well with Fragments and you can create as many of them as you need. I even had a project where I had 80 fragments created with a FragmentStateAdapter.
Second of all, you need a newInstance constructor for your fragments as a safe pattern. If you don't use that, you might get some exceptions later in your app, but other than that you will also use the function to pass your data to each fragment.
Here is how I do it:
class FragmentStateAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle,
car: Car
) : FragmentStateAdapter(fragmentManager, lifecycle) {
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> InfoFragment.newInstance(car)
1 -> AboutFragment.newInstance(car)
else -> LocationFragment.newInstance(car)
}
}
Basically I am passing your car object (presuming it's the only data object that needs to reach the fragments) as a parameter to the FragmentStateAdapter, locking the itemCount to 3 because you have 3 fragments and then returning an instance of each fragment in the override method createFragment(). Now, creating the instance functions:
class InfoFragment : Fragment() {
companion object {
private const val BUNDLE_KEY = "car_object"
fun newInstance(car: Car) = InfoFragment().apply {
arguments = Bundle().apply {
putParcelable<Car>(BUNDLE_KEY, car)
}
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_car_details_info, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val car = arguments.getParcelable<Car>(BUNDLE_KEY)
}
}
And this is how you get your car object inside your fragments. Basically in the newInstance() function you create a new instance of your fragment and pass data to it via bundle. Inside your activity this is how you initialise your adapter:
val adapter = FragmentStateAdapter(supportFragmentManager, lifecycle, car)
Now, about the replacement for the tab view, do a custom LinearLayout like this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="1.0">
<Button
android:id="@+id/button1"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.333"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:text="Details"
android:textAllCaps="true" />
<Button
android:id="@+id/button2"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.333"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:text="About"
android:textAllCaps="true" />
<Button
android:id="@+id/button3"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.333"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:text="Locations"
android:textAllCaps="true" />
and then set the initial color of the buttons after your own preference with android:background="#COLOR_OR_CHOICE"
In activity, add the three buttons in an ArrayList of buttons and use this listener on the viewPager2:
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
// here, get the position of the viewPager2 and change the color of the current button using the arrayList index that matches the viewPager2 position.
})
Then, on every button's click listener, do viewPager2.setCurrentItem(position_you_want_to_go_to, true) where true is a check for a smooth scroll.
Hope this helps.
Upvotes: 1
Reputation:
Shouldn't you be assigning the values to the fields in your fragment?
Upvotes: 0