How to avoid duplicate code in the activity and fragment? - android

When an activity is launched, the condition is checked in it, if the condition is true, the openNewActivity method is called and the current activity ends. But if the condition is false, then a transition is made to the fragment, inside which this condition can become true and then you need to call the openNewActivity method, which is defined in the activity. I do not want to duplicate this method in the fragment, how to properly implement a call to this method from the fragment? What are the best practices in such cases?

Activity
class FirstActivity : AppCompatActivity(), MyInterface {
override fun onSomethingDone() { //This function gets called when the condition is true
openNewActivity()
}
override fun onAttachFragment(fragment: Fragment) { //A fragment MUST never know it's an activity, so we exposed fragment interface member to easily initialize it whenever the fragment is attached to the activity.
when (fragment) {
is MyFragment -> fragment.myInterface = this
}
}
override fun onCreate() {
super.onCreate()
}
private fun openNewActivity() {
//opens a new activity
}
}
Interface
interface MyInterface {
fun onSomethingDone()
}
Fragment
class MyFragment : Fragment() {
var myInterface: MyInterface? = null
override fun onCreate() {
if (somethingIsTrue)
myInterface.onSomethingDone() //condition is true, call the interface method to inform the activity that the condition is true and the new activity should be opened.
}
}
Create an interface. Initialize the fragment's interface in the activity's onAttachFragment for the reason mentioned in the code. This way, the function for starting a new activity is defined only in the activity and does not need to be duplicated in the fragment.

Related

How to tell a fragment that an Activity was finished?

When an action is performed I am finishing the activity that I am currently on and landing on the fragment that was previously opened.
My question is, is there a way to tell that fragment that is being resumed from finishing a specific activity?
You can use LiveData approach.
Create a constant in companion object in fragment class as follow:
companion object {
val _isActivityFinished = MutableLiveData<Boolean>()
val isActivityFinished: LiveData<Boolean> get() = _isActivityFinished
}
In onDestroy() of your activity
override fun onDestroy() {
_isActivityFinished.postValue(true)
super.onDestroy()
}
In fragment, observe the state of this variable
isActivityFinished.observe(viewLifecycleOwner) {
if (it) {
// do your work.. it has been returned from activity
}
}

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

How to bind external library class with callback in Hilt?

I'm a beginner in Hilt. I have a library which takes in an interface. The library does some operation and invokes the interface callback. I have an activity which invokes this library by passing the interface implementation. I'd like to know how to inject this using Hilt.
Interface in library
interface InterfaceInLibrary() {
fun callback1()
fun callback2(/*params */)
}
Activity
class MyActivity: InterfaceInLibrary() {
override fun onCreate(savedInstanceState: Bundle?) {
//library initialization
val myLibraryClass = MyLibraryClass.getInstance(this) //passing the InterfaceInLibrary implementation
}
override fun callback1() {
Toast.makeText(this, "callback1", Toast.LENGTH_LONG).show()
}
override fun callback2() {
Toast.makeText(this, "callback2", Toast.LENGTH_LONG).show()
}
}
I would like to know how to inject MyLibraryClass in MyActivity using Hilt.
The only possible way I know (or at least how I am handling this use-case in my projects) is to field inject the concrete class that invokes the interface and then let the activity implement the concrete class and inherit from the callback. Since your interface and your concrete class look kinda weird, I will provide a full implementation here. Let's assume we have the following interface:
Interface
interface IMyCallbackInterface {
fun callbackWithoutParameters()
fun callbackWithParameters(value: String)
}
Then, you need some class to invoke this callback. In my case, this was always a recylerview.adapter, but we will use somethin easier:
Invoking class
class MyInvokingClass #Inject constructor() {
// this interface will be initialized by our activity
private lateinit var callbackListener: IMyCallbackInterface
// This function invokes the first callback
fun someFunctionThatInvokesCallbackWithoutParameters() {
// do some stuff
callbackListener.callbackWithoutParameters()
}
// This function invokes the second callback
fun someFunctionThatInvokesCallbackWithParameters() {
// do some stuff
callbackListener.callbackWithParameters(value = "Hello")
}
// This will be called from our activity to initialize the callback
fun initializeCallback(callbackOwner: IMyCallbackInterface) {
this.callbackListener = callbackOwner
}
}
Then, you need to field inject the class and inherit from the callback inside your activity
Activity or Fragment
class MyActivity : IMyCallbackInterface {
#Inject lateinit var invokingClass: MyInvokingClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate()
setContentView(...)
// Since MyActivity implements the interface
// it is an instance of it. So you can simply
// say, that the "owner" of the callback is the activity
invokingClass.initializeCallback(this#MyActivity)
}
override fun callbackWithoutParameters() {
// do some stuff
}
override fun callbackWithParameters(value: String) {
// do some stuff with string
}
}
Because our Activity inherits from the callback and we said in onCreate() that the interfaceOwner of MyInvokingClass is the activity, every time the callback gets invoked, the interface functions inside the activity will be invoked as well.

Android kotlin - how to create a property delegate for toolbar backbutton press

I have a fragment that has a toolbar and i currently set the back button listen like this:
with(myToolBarView) {
(activity as AppCompatActivity).setSupportActionBar(this)
setNavigationOnClickListener {
requireActivity().onBackPressed()
}
}
but when i have other fragments this code gets repeated all the time. so i wish i could do something like this:
myToolBarView by backButtonDelegate(this)
but the problem is for one, the view wont be ready until after onCreateView gets called. So i'll get null or some exception if i try to access the view before its ready. so then how i can do this?
i tried using #OnLifecycleEvent(Lifecycle.Event.ON_CREATE) but i don't see how that will help me and also i need code called every time onoCreateView is called incase the fragment view is re-created.
update: i now [realize the lifecycleowner of a fragment works with the fragment view lifecycle][1]
so here is what i have so far:
class BackBtnDelegate(val fragment: Fragment) {
// operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
// return "$thisRef, thank you for delegating '${property.name}' to me!"
// }
init {
fragment.lifecycle.addObserver(object : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onDestroy() {
with(myToolBarView) {
(activity as AppCompatActivity).setSupportActionBar(this)
setNavigationOnClickListener {
requireActivity().onBackPressed()
}
}
}
})
}
// operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
// // println("$value has been assigned to '${property.name}' in $thisRef.")
// }
notice i dont even need the set or get propertty values here as they become meaningless and still i dont have a reference to myToolBarView so how can i accomplish this ?
[1]: Fragment lifecycle events in LifeCycleAware Fragment
That is not how the Delegation pattern works. Quote:
In delegation, two objects are involved in handling a request: a receiving object delegates operations to its delegate
You could create an extension function for the Fragment class:
fun Fragment.setToolbar(myToolbar: Toolbar) {
with(myToolbar) {
(activity as AppCompatActivity).setSupportActionBar(this)
setNavigationOnClickListener {
requireActivity().onBackPressed()
}
}
Or you could have common functions in a BaseFragment and have other Fragments implement that class, but I suppose you already know that.
class BaseFragment : Fragment() {
...
protected fun setToolbar(myToolbar: Toolbar) {
with(myToolbar) {
(activity as AppCompatActivity).setSupportActionBar(this)
setNavigationOnClickListener {
requireActivity().onBackPressed()
}
}
}
...
}

onActivityCreated is deprecated, how to properly use LifecycleObserver?

Google deprecate fragment’s onActivityCreated() on Android and recommend to use LifeCycleObserver:
To get a callback specifically when a Fragment activity's
* {#link Activity#onCreate(Bundle)} is called, register a
* {#link androidx.lifecycle.LifecycleObserver} on the Activity's
* {#link Lifecycle} in {#link #onAttach(Context)}, removing it when it receives the
* {#link Lifecycle.State#CREATED} callback.
So I try to make it in recommended way, but only state I can observe in Logcat is just State: INITIALIZED.
private lateinit var lifecycleObserver: LifecycleObserver
override fun onAttach(context: Context) {
super.onAttach(context)
hostActivity = context as HostActivity
lifecycleObserver = object : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate() {
Logger.tag("SOME-TAG")d("State: ${lifecycle.currentState}")
if(lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
Logger.tag("SOME-TAG").d("CREATED")
hostActivity.lifecycle.removeObserver(lifecycleObserver)
}
}
}
hostActivity.lifecycle.addObserver(lifecycleObserver)
}
What is wrong in code above?
UPDATE 1: Looks like I forgot to use hostActivity.lifecycle.currentState and checked fragment's lifecycle instead of Activities lifecycle.
UPDATE 2: Suggested by Google approach not worked for
1 Host activity and 2 fragments when you click back button from one to another, cause onAttach never called, but onActivityCreated called.
As per the changelog here
The onActivityCreated() method is now deprecated. Code touching the
fragment's view should be done in onViewCreated() (which is called
immediately before onActivityCreated()) and other initialization code
should be in onCreate(). To receive a callback specifically when the
activity's onCreate() is complete, a LifeCycleObserver should be
registered on the activity's Lifecycle in onAttach(), and removed once
the onCreate() callback is received.
You can do something like this in your fragment class:
class MyFragment : Fragment(), LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreated() {
// ... Your Logic goes here ...
}
override fun onAttach(context: Context) {
super.onAttach(context)
activity?.lifecycle?.addObserver(this)
}
override fun onDetach() {
activity?.lifecycle?.removeObserver(this)
super.onDetach()
}
}
All I needed was onActivityCreated(...), hence I did implement an observer that:
Automatically removes itself (using .removeObserver(...)).
Then calls passed callback (update()).
I did it in next way:
class MyActivityObserver(
private val update: () -> Unit
) : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
owner.lifecycle.removeObserver(this)
update()
}
}
and use it in fragments onAttach (or another lifecycle method) like:
myActivity.lifecycle.addObserver(MyActivityObserver {
myOnActivityCreated()
})
You can consider the Lifecycle.State as the nodes in a graph and Lifecycle.Event as the edges between these nodes.
So you will never reached the State.Created on your ON_CREATE function.
Solution
class YourFragment : Fragment(), LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onCreated(){
Log.i("tag","reached the State.Created")
}
override fun onAttach(context: Context) {
super.onAttach(context)
lifecycle.addObserver(this)
}
override fun onDetach() {
super.onDetach()
lifecycle.removeObserver(this)
}
}
For more details
https://developer.android.com/topic/libraries/architecture/lifecycle#lc
The best way to solve the issue is to use lifecycleScope which is present in the activity lifecycle. Below is the code snippet
override fun onAttach(context: Context) {
super.onAttach(context)
activity?.lifecycleScope?.launchWhenCreated {
setupActionbar()
}
}
How does it work? launchWhenXxx runs the launch block when it automatically reaches the specified state(in this case it is Created) and if the lifecycle goes to the destroyed state it cancels the launched coroutine automatically. Internally lifecycleScope uses Dispatchers.Main.immediate and hence there is no penalty of thread switching
Pros of this approach are following:
You don't have to manually maintain registering and deregistering of the observer
No need to overwrite two lifecycle methods
You have to latest activity and fragment dependencies to use lifecycleScope attached to the lifecycle
onActivityCreated is deprecated in API level 28.
use onViewCreated for code touching the view created by
onCreateView and onCreate for other initialization. To get a
callback specifically when a Fragment activity's onCreate is called,
register a androidx.lifecycle.LifecycleObserver on the Activity's
Lifecycle in onAttach, removing it when it receives the CREATED
callback.
The annotation #OnLifecycleEvent is deprecated too.
This annotation required the usage of code generation or reflection,
which should be avoided. Use DefaultLifecycleObserver or
LifecycleEventObserver instead.
So, to fix the issue with the deprecated onActivityCreated and OnLifecycleEvent annotation you should do the following:
Implement DefaultLifecycleObserver.
Register your class as observer in onAttach().
Override onCreate(owner: LifecycleOwner) and move your code from onActivityCreated in it.
De-register the observer when the CREATE event is received in onCreate()
See Kotlin and Java examples below:
Kotlin:
class YourFragment : Fragment(), DefaultLifecycleObserver {
override fun onAttach(context: Context) {
super.onAttach(context)
// Register your class as observer
activity?.lifecycle?.addObserver(this)
}
override fun onCreate(owner: LifecycleOwner) {
super<DefaultLifecycleObserver>.onCreate(owner)
// Remove the observer
activity?.lifecycle?.removeObserver(this)
//Move here your code from onActivityCreated(savedInstanceState: Bundle?)
}
}
Java:
public class YourFragment extends Fragment implements DefaultLifecycleObserver {
public void onAttach(#NonNull Context context) {
super.onAttach(context);
// Register your class as observer
if (getActivity() != null) {
getActivity().getLifecycle().addObserver(this);
}
}
#Override
public void onCreate(#NonNull LifecycleOwner owner) {
DefaultLifecycleObserver.super.onCreate(owner);
// Remove the observer
if (getActivity() != null) {
getActivity().getLifecycle().removeObserver(this);
}
//Move here your code from onActivityCreated(savedInstanceState: Bundle?)
}
IMPORTANT: Note that onActivityCreated is called after onCreateView, but DefaultLifecycleObserver.onCreate is called before onCreateView. So, if until now you were using in onActivityCreated something initialised in onCreateView, you'll have to move it somewhere else. E.g. in onViewCreated().

Categories

Resources