I'm new in kotlin and android development.
I was following a tutorial, in that tutorial in a fragment class, companion object, defined a method named newInstance() that returned a fragment, the method was never used.
class myFragment : Fragment(){
companion object {
fun newInstance(foo:Int): myFragment {
val fragment = myFragment()
val args = Bundle()
args.putString("foo", foo)
fragment.arguments = args
return fragment
}
}
}
Is that okay?
Is that going to call it automatically or should I call it somewhere?
(sorry if the explanation isn't good)
It not gonna be used automatically, it is just one of the ways to create fragments.
Basically you need to call this function in the place where you wish to add/ replace this fragment into it's container with the help of FragmentManager
You need to use supportFragment manager in your activity to replace the fragment
val transition = supportFragmentManager.beginTransaction()
transition.addToBackStack("Your_fragment_unique_tag")
transition.replace(containerViewId, fragment).commit()
containerViewId will be FrameLayout id in your activity which is the container for replacing fragment i.e R.id.mainContainer
Related
I have a Fragment within a ViewPager that is created like this:
companion object {
fun newInstance(someData: SomeData): ViewPagerFragment {
val f = ViewPagerFragment()
val args = Bundle()
args.putParcelable("someData", someData)
f.arguments = args
return f
}
}
It is also a Hilt entry point, and it creates its own ViewModel, but also needs the parent Fragments ViewModel, so the top of the class looks like this:
#AndroidEntryPoint
class ViewPagerFragment : Fragment() {
private val parentViewModel: ParentViewModel by viewModels(
ownerProducer = { requireParentFragment() }
)
private val viewPagerViewModel: ViewPagerViewModel by viewModels()
However, the parent Fragment is not found:
java.lang.IllegalStateException: Fragment ViewPagerFragment{b6d6157}
(e5f3fc7d-2ae4-4275-9763-22826a9be939) id=0x7f09017e} is not a child Fragment, it is
directly attached to
dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper#1852444
I think this is happening because after adding the viewPagerViewModel the generated ViewPagerFragment class is not a childFragment. Is there a workaround to get the parent Fragment and ultimately get the parent ViewModel? The latter is the main goal.
For now I use ViewPager, not ViewPager2, because it's a legacy infinite wrapper ViewPager.
EDIT:
The ViewPagerAdapter is created like this:
val pagerAdapter = VpPagerAdapter(
requireActivity().supportFragmentManager,
someData,
)
When you create your ViewPager adapter, you need to pass in the childFragmentManager if you want the fragments in the ViewPager to be considered child fragments.
I'm using a shared view model like here
But the problem is that when I clear my last fragment, I want to clear the viewmodel, or kill its instance, but somehow it survives when I leave the last fragment that uses it
How can I programatically clear this viewmodel ?
I use it like this
Fragment A
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated() {
model.getTotal().observe(viewLifecycleOwner, Observer { cartTotal ->
total = cartTotal
})
}
From fragment B I sent the total
Fragment B
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated() {
model.setTotal = 10
}
But when leaving Fragment A with that data (doing popBackStack since I'm using navigation components) it does not clear the viewmodel, instead when I open again my fragment , the data stills there
I suspect that the viewmodel is tied with my Container Activity and not the lifecycle of the fragments itself, so
How can I remove the instance or clear my viewmdel when I hit my last fragment ?
Thanks
If you want to get a ViewModel associated with a parent fragment, your inner fragment should follow the by viewModels JavaDoc and use:
val viewmodel: MYViewModel by viewmodels ({requireParentFragment()})
This says to use the parent Fragment as the owner of your ViewModel.
(The parent fragment would use by viewModels() as it is accessing its own ViewModels)
you can also clear viewModelStore manually after Fragment A destroyed.
something like this :
override fun onDetach() {
super.onDetach()
requireActivity().viewModelStore.clear()
}
then your viewModel instance will be cleared. for checking this work you can debug onCleared method of your viewModel.
I found the question but does not have solution in code
I want to have data when backpress/manual back happens. I am using navigateUp() to go back. How can I pass data to previous fragment? navigateUp() does not have any facility to pass data to previous fragment. Even I did not find solution using Safe Args. It's passing data forward. I want to have in backward Frad B -> Frag A.
My code to go back to previous fragment
Navigation.findNavController(view).navigateUp()
My question is, How can i get data in previous fragment. I can navigate to Frag A from Frag B using
According to developer.android.com, you can use common for fragments where you want to share data ViewModel using their activity scope.
Here are steps:
Create view model which will keep the data:
class SharedViewModel : ViewModel() {
val dataToShare = MutableLiveData<String>()
fun updateData(data: String) {
dataToShare.value = data
}
}
Observe data changes in Fragment1:
class Fragment1 : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(activity!!).get(SharedViewModel::class.java)
viewModel.dataToShare.observe(this, Observer<String> { dataFromFragment2 ->
// do something with data
})
}
}
Update data in Fragment2 and if you're handling navigation properly, now, you should be able to receive data changes on Fragment1:
class Fragment2 : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(activity!!).get(SharedViewModel::class.java)
updateDataButton.setOnClickListener { v ->
viewModel.updateData("New data for fragment1")
}
}
}
I hope answer helps.
You can use NavigationResult library. Basically it's startActivityForResult but for Fragments in Navigation component.
Please use the OFFICIAL androidx's components. setFragmentResultListener() and setFragmentResult() methods:
implementation "androidx.fragment:fragment-ktx:1.3.5"
Cheers ;)
You should use static variables/companion objects, because it is better than shared viewmodel as it is not simple/nice architecture. As it it not straightforward, I think it is the best way.
To navigateUp From FragmentB to FragmentA
FragmentB:
isBackpressed = true
findNavController().navigateUp()
FragmentA:
onViewCreated() {
// todo
if(isBackpressed) {
isBackpressed = false
// do whatever you want
}
}
To pop destinations when navigating from one destination to another, add an app:popUpTo attribute to the associated <action> element.
To navigate from fargment2 to Fragment1 with arguments, specify in the navigation graph the action of the caller fragment and the arguments of the destination fragment :
<fragment
android:id="#+id/fragment2"
android:name="com.example.myapplication.Fragment2"
android:label="fragment_2"
tools:layout="#layout/fragment_2">
<action
android:id="#+id/action_2_to_1"
app:destination="#id/fragment1"
app:popUpTo="#+id/fragment1"/>
</fragment>
<fragment
android:id="#+id/fragment1"
android:name="com.example.myapplication.Fragment1"
android:label="fragment_1"
tools:layout="#layout/fragment_1">
<argument
android:name="someArgument"
app:argType="string"
app:nullable="false"
android:defaultValue="Hello Word"/>
</fragment>
In your Fragment2 class, you call your action and pass your argument:
val action = Fragment2Directions.action2To1("MY_STRING_ARGUMENT")
findNavController().navigate(action)
You can just call
findNavController().navigate(R.id.fragment1, args)
where args is your bundle.
In fragment1, fetch the data from the arguments
I'm trying to pass a bundle of object instances down from my main activity to the first fragment in a chain of other fragments using the NavHostFragment. I've tried all sorts but the bundle always seems to be null once it reaches the first fragment.
Here's how I'm initiating the NavHostFragment (frameContainer is a Frame Container element in my layout xml)
NavHostFragment navHost = NavHostFragment.create(R.navigation.claim_nav_graph);
getSupportFragmentManager().beginTransaction()
.replace(R.id.frameContainer, navHost)
.setPrimaryNavigationFragment(navHost)
.commit();
The documentation says there are 2 different .create functions, one of them you can pass a second arguments to as a bundle, but Android Studio doesn't allow me to use this version.
Does anyone have any ideas?
Thanks in advance!
It does seem to be a flaw with the NavHostFragment, passing data down to the first fragment does not seem to be possible, as the Bundle you can set as a second argument on the create function is overwritten along the way.
In the end I resolved this by building the bundle in the first fragment of the activity instead. I was able to access the activities intent properties using the below.
// Kotlin
activity.intent?.extras?.getBundle(KEY_BUNDLE_ID)
// Java
getActivity().getIntent().getBundleExtra(KEY_BUNDLE_ID)
This was enough of a workaround for me in this situation, but it would be great if it was possible
If you're using viewModels, you can do this:
your viewmodel:
class NiceViewModel: ViewModel() {
var dataYouNeedToPass = "initialValue"
}
your activity:
class MainActivity : AppCompatActivity() {
val niceViewModel: NiceViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
niceViewModel.dataYouNeedToPass = "data You Need To Pass"
}
}
your fragment:
class YourFragment : Fragment() {
private lateinit var niceViewModel: NiceViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
niceViewModel = (activity as MainActivity).niceViewModel
niceViewModel.dataYouNeedToPass //do whatever you need to do with this
}
}
I am trying to implement a simple TimePickerDialogue fragment, which displays when a button in pressed in a layout which is also a fragment. The Android developer guide shows this in Java:
public void showTimePickerDialog(View v) {
DialogFragment newFragment = new TimePickerFragment();
newFragment.show(getSupportFragmentManager(), "timePicker");
}
and says:
The show() method requires an instance of FragmentManager and a unique name for the fragment.
When I converted this to Kotlin there is no getSupportFragmentManager method offered. What should I use instead?
class AlertsFragment : Fragment() {
fun showTimePickerDialog(v: View) {
val newFragment = TimePickerFragment()
newFragment.show(FragmentManager(), "timePicker") // WHAT FRAGMENTMGR???
}
}
I am importing android.support.v4.app.Fragment
My MainActivity will display the TimePickerDialogue fragment as well as the Fragment that has a button to open the TimePickerDialogue. MainActivity has a tablayout using fragments.
Does anything need to be changed in MainActivity to make the TimePickerDialogue show() function work in the AlertsFragment?
class MainActivity : FragmentActivity(){
private lateinit var pagerAdapter: TabPagerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pagerAdapter = TabPagerAdapter(supportFragmentManager)
pagerAdapter.addFragments(WorkoutGridFragment(), "Workouts")
pagerAdapter.addFragments(AlertsFragment(), "Reminders")
pagerAdapter.addFragments(AboutFragment(), "About")
// customViewPager is the viewpager created in the activity_main xml layout
customViewPager.adapter = pagerAdapter
// set up the viewpager with the tablayout
customTabLayout.setupWithViewPager(customViewPager)
}
}
Fragments don't have a SupportFragmentManager field or getter. This applies whether you write it in Kotlin or Java.
Activities, however, do. So call the activity and get the supportFragmentManager:
fun showTimePickerDialog(v: View) {
val newFragment = TimePickerFragment()
newFragment.show(activity.supportFragmentManager, "timePicker")
}
Also, if this is what you were reading in the docs, you'll see this:
Also make sure that your activity that displays the time picker extends FragmentActivity instead of the standard Activity class. (emphasis mine)
Which implies the showTimePickerDialog method is defined in an Activity. And don't get this wrong, I'm not saying you have to define it in an activity, but since that's what they do in the docs, they can call the SupportFragmentManager directly. But if you call it from a fragment or anywhere outside an activity, you need an activity instance.