How to share properties and methods between Fragments in Main Activity - android

I have two fragments in my MainActivity, I want to make the first fragment inherit a method from the second fragment. Is this possible?

One approach might be to make both fragments delegate to the same object, which is more inline with the principle of preferring composition over inheritance.
Here is an example:
class FragmentA : Fragment(), SharedMethodsDelegate by SharedMethods {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedMethod()
}
}
class FragmentB : Fragment(), SharedMethodsDelegate by SharedMethods {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedMethod()
}
}
interface SharedMethodsDelegate{
fun sharedMethod()
}
object SharedMethods : SharedMethodsDelegate {
override fun sharedMethod() {
print("hey")
}
}
This approach would have the advantage of allowing you to use different base classes for your fragments. You could even use it to share methods between a fragment and an activity.

Another method is to extract that logic into another object, and then use that object in both fragments. Unless the method is directly related to view manipulation, this would be what I suggest. It also makes the method more testable.

Not without both Fragments sharing the same parent. you can create an instance of Fragment and call it BaseFragment, and then have both fragments that you're creating be children of BaseFragment. It won't be the same instance, but you'll have access to the same methods
Fragment
|
BaseFragment <- you create this and put the methods you want to share here
/\
/ \
FragmentA FragmentB

Related

How to pass a button click from Fragment that is in a BottomSheet Toolbar to the Main Activity?

I have implemented a TabLayout (which uses fragments) in my bottom sheet toolbar that has buttons which should affect the Main Activity. How do I pass the button clicks from the fragments in my TabLayout to the Main Activity?
I'm stuck and I don't know where to start.
There are multiple methods to do communication between fragments and its activity . I'll explain the ones which are used widely.
Using an interface.
Using a SharedViewModel for all your fragments and its activity . ( this can be used if you are implementing MVVM architecture )
check this out link
EDIT :
This is a simple step by step implementation on how to pass data from a fragment to activity . I am just using dummy class names and method parameters .
Create a folder called 'listeners' inside your app module , this is where you should have all your interface classes. ( This is just for a clean approach , if that is not your priority then you can save the interface class anywhere ). for Eg I am making TabLayoutFragmentClickListner.
interface TabLayoutFragmentClickListener {
}
Add a method to this interface . This is the method which would be called when we click a button inside the fragment. add the required parameters which needs to be passed from fragment to the activity. In this case I am just using a String.
interface TabLayoutFragmentClickListener {
fun onLayoutFragmentClick(value : String)
}
Implement this interface in the activity in which you want the data to be received.
class MainActivity : AppCompatActivity() , TabLayoutFragmentClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
This would make you override the interface method inside that activity.
class MainActivity : AppCompatActivity() , TabLayoutFragmentClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onLayoutFragmentClick(value: String) {
Log.d("testing" , value)
}
}
This overiden method is the definition for your interface method in this activity. Hence when you access the interface method from your fragment, the overidden method inside the activity would be called. Try understanding how interface works in java or kotlin.
Now initialise the instance of the listener in your fragment's onAttach method. like this
class TestingFragment : Fragment() {
lateinit var listener: TabLayoutFragmentClickListener
override fun onAttach(context: Context) {
super.onAttach(context)
listener = context as TabLayoutFragmentClickListener
}
}
now call the interface method from your fragment with the required parameter. This would hence trigger the interface method definition in your activity hence passing data from the fragment to the activity.
class TestingFragment : Fragment() {
lateinit var listener: TabLayoutFragmentClickListener
override fun onAttach(context: Context) {
super.onAttach(context)
listener = context as TabLayoutFragmentClickListener
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
test_btn.setOnClickListener {
listener.onLayoutFragmentClick("testing string")
}
}
Hope this helps.
I think You need to create a function in MainActivity And call that function in the fragment. You can easily access that function bcoz it is your parent activity so you can use it on the button.
References:
I know this is in java but I think it's helpful for you link

Does an instance of a SharedViewmodel never dies?

I have an app that has a main activity and fragments depend on it, so this is normal.
Now, two of my 10 fragments need to communicate, which I use the example given here
https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
// Update the UI
})
}
}
Now, if MasterFragment and DetailFragment dies (both does a popBackStack()) does that instance of the viewmodel keep active untill I finish the MainActivity containing this Fragments ? Because now I dont need anymore that viewmodel instance, but as per documentation says, this instance will be retained from the Activity that contains these fragments
This is not what I'm looking for to communicate between fragments since now a new instance of that viewmodel will be the same as the past one I have created, I mean, it will reuse the instance that I used with the already poped fragments, in which I will need to extra handling a deletion or reset of all the data inside this viewmodel instead of getting a new fresh viewmodel.
Does it works this way or that instance automatically dies when no fragments depending on it are in the stack anymore ?
Now, if MasterFragment and DetailFragment dies (both does a popBackStack()) does that instance of the viewmodel keep active untill I finish the MainActivity containing this Fragments ?
Correct. While it so happens that only two of your fragments use it, that ViewModel is scoped to the activity.
I mean, it will reuse the instance that I used with the already poped fragments, in which I will need to extra handling a deletion or reset of all the data inside this viewmodel instead of getting a new fresh viewmodel.
Then perhaps you should not be using activityViewModels(). For example, you could isolate these two fragments into a nested navigation graph and set up a viewmodel scoped to that graph.
Does it works this way or that instance automatically dies when no fragments depending on it are in the stack anymore ?
The ViewModel system does not know about what is or is not "depending on it". It is all based on the ViewModelStore and the ViewModelStoreOwner that supplies it. activityViewModels() uses the activity as the ViewModelStoreOwner, so viewmodels in that ViewModelStore are tied to the activity.

How can I decrease a method count in my activity in MVP pattern?

I build my app in MVP architecture and I have a trouble with many functions in my activity and presenter. How Can I decrease a method count?
I have already heard about some solutions:
Split a big presenter into smaller ones but then I would have to create another methods in my activity for presenters connection.
Create a new class and create it instance in my activity which would implement the View interface and will require all of the views needed to manage the presenters. But I am not convinced to this solution. I think it may add another mess to my architecture.
Do you have other ideas or advantages/disadvantages about one described above?
There is more than a way to reduce methods from your Activity/Fragment
One is called inheritance, where you can extend abstract methods into your main Activity/Fragment class and manage the lifecycle from there.
For example, using BaseActivity or BaseFragment you can have more than one method inside of it and just extend that into your main Activity or Fragment
BaseActivity.kt
abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_NO_TITLE)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
super.onCreate(savedInstanceState)
setContentView(getLayout())
}
#LayoutRes
abstract fun getLayout(): Int
fun Context.toast(message: String?, toastDuration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, toastDuration).show()
}
override fun onDestroy() {
super.onDestroy()
//Do here what you want
}
override fun onStop() {
super.onStop()
//Do here what you want
}
override fun onStart(){
super.onStart()
//Do here what you want
}
override fun onPause() {
super.onPause()
//Do here what you want
}
override fun onRestart() {
super.onRestart()
//Do here what you want
}
}
This BaseActivity extends AppCompatActivity(), that means that you can manage the lifecycle of your Activity in this class, and then, just extend it in your main Activity, when you do this, all the functionality inside your BaseActivity will be applied to your MainActivity class, if you want to change or override something, just call the methods from that abstract class.
MainActivity.kt
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//You dont need setContentView since we do all the configuration in the BaseActivity
toast("This is a message with a toast since we implemented thi into the BaseActivity we do not need to do toasts all over again")
}
override fun getLayout(): Int {
return R.layout.activity_login_view
}
//For example, if you want to override the functionallity from a method inside your BaseActivity you can implement it like always
override fun onRestart() {
super.onRestart()
//Replace what BaseActivity onRestart() does
}
Doing this, you can have more than 1 method of your Activity inside your BaseActivity, this will reduce the methods inside your class that inherits from BaseActivity, also, if you need this to work with Fragments, just extend Fragment instead of AppCompatActivity an make a class called BaseFragment
Also, adding an interface for view operations and presenter operations is a great way to organize our apps, you can take a look at an example I'm making for login on Github

Shared viewModel achived fragment lifecycle

How I can use shared viewModel with fragments without activity?
Like in code but in place of requireActivity() use ParentFragment. In this case when ParentFragment will destroyed, SharedViewModel is cleared, but when I provide SharedViewModel from activity, it not cleared when ParentFragment destroyed.
And I use Navigation Components, which mean that I can`t set tag for fragment and then use findFragmentByTag()
class ParentFragment:Fragment{
override fun onCreate(savedInstanceState: Bundle?) {
var viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class)
}
}
class ChildFragment:Fragmnet{
override fun onCreate(savedInstanceState: Bundle?) {
var viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class)
}
}
You can try scoped-vm - it allows you to request ViewModel for scope identified by a String key. Scope lives till the last fragment that requested ViewModel gets destroyed, then ViewModel gets cleared.
You can use this code to obtain SharedViewModel both in ParentFragment and ChildFragment.
ScopedViewModelProviders
.forScope(this, "scope")
.of(requireActivity())
.get(SharedViewModel::class.java)
See you can initialize viewModel in all fragments which you want to share viewmodel, and use Rx with viewModel, your all process in these fragments will keep running until you want to cancel it,you can call viewModel.oncleard() from Activity or Fragment.
public override fun onCleared() {
super.onCleared()
compositeDisposable.dispose()
// or cancel any process
}
If you do not know ViewModel Scope, please check this image

How can I pass data to the first Fragment whilst using the Navigation Architecture?

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
}
}

Categories

Resources