How to bind external library class with callback in Hilt? - android

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.

Related

Reuse methods in Kotlin, Android

I need to open one activity from several different points in the app. Let's say from Settings fragment, Main Activity and Navigation drawer (fragment). I don't want to copy/paste the same method and the method is very specific, it should be exactly the same (because it registeres Firebase events). How to structure the code in effective way? Where to put this method? One idea is to have a global ActivityUtils.kt file with just methods and it would be used to store these methods. I'm interested in the alternatives and what are pros and cons of each.
I would create a companion object in the Activity you need to open:
class YourActivity : AppCompatActivity() {
companion object {
fun start(ctx: Context) {
// put your logic here (registering of Firebase events)
val i = Intent(ctx, YourActivity::class.java)
ctx.startActivity(i)
}
}
}
And call it from another activity:
YourActivity.start(this)
or from another fragment:
YourActivity.start(context)
Use an extension method:
fun Activity.doMyStuff() {}
That can be called from any class extending Activity:
doMyStuff()
Extension functions like this shouldn't go inside a class, but rather inside a file. So if you were to make an ActivityUtils.kt file, don't have any sort of class ActivityUtils {} stuff in it. The function(s) should just go directly into the file.
Why not to use MVP?
Like,
interface IView {
val context: Context
}
interface IPresenter {
fun launchActivity(view: IView)
}
class MyActivityModel
{
var key = "key"
/*some other data*/
fun getParcelableObject(): Parcelable
{
return /*some parcelable from model data*/
}
}
class MyActivity : AppCompatActivity(), IView
{
override val context: Context
get() = this
}
class MyActivityPresenter() : IPresenter
{
private var model: MyActivityModel = MyActivityModel()
override fun launchActivity(view: IView)
{
val intent = Intent(view.context, MyActivity::class.java)
intent.putExtra(model.key, model.getParcelableObject())
view.context.startActivity(intent)
}
fun setSomeDataToModel(someData: Any) {
}
}
/*Everyone who wants to use presenter, must be a Context and implement IView*/
fun use()//in some fragment, or activity implementing IView
{
MyActivityPresenter().launchActivity(this)
//or
val presenter = MyActivityPresenter()
presenter.setSomeDataToModel("some data")
presenter.launchActivity(this)
}

In Kotlin, how does a constructor with anonymous class work?

Consider a functionality F, which depends on Android lifecycle methods. I have implemented this functionality in an Activity A. Any other activity which wants to implement this functionality can simply extend A. The results are sent back to the child activity via an interface. Example:
// interface
interface ACallbacks {
fun onResult(string: String)
}
// Activity A
open class AActivity
(private val aCallbacks: ACallbacks): AppCompatActivity() {
// functionality F, which depends on Android lifecycle methods
}
// Activity B
class BActivity: AActivity(object: ACallbacks {
override fun onResult(string: String) {
// Q: how to use string in BActivity?
}
}) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_b)
}
}
How to access the result from Activity A (i.e., string) in Activity B. For example, how do I set it in a textView present in Activity B?
If BActivity inherits from AActivity - inside BActivity you can get what you want:
override fun someFun(): String {
val parentResult = super.someFun()

Reacting to activity lifecycle in ViewModel

I'm trying to create an app which will use MVVM architecture and there's one thing I quite don't understand.
Official Android docs say that's not a good idea to reference activity context in ViewModel's (as ViewModel may outlive activity) so I've started to wonder about usecase when I want to execute some action when my activity is resumed.
I know ViewModel's shouldn't do business logic themselves but even if I use some service class (let's say GPSService which has to start and pauseeach time activity is resumed on paused), and inside this service I react to activity onResume (using Lifecycle observer) I will still reference this activity from ViewModel as I'm referencing service which holds reference to activity being observed, this may cause activity leak (correct me if I'm wrong).
So my question is, how to react to activity or fragment lifecycle in MVVM architecture?
If you need to have a ViewModel be lifecycle aware, then you can have it implement LifeCycleObserver and override life cycle events as necessary. Example,
public class MyModel extends ViewModel implements
LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
protected void onLifeCycleStop() {
// do something
}
}
In the activity or fragment then you can add the view model to the activity life cycle owner.
public class MyActivity extends AppCompatActivity {
protected MyModel mMyModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMyModel = ViewModelProviders
.of(this)
.get(MyModel.class);
getLifecycle().addObserver(mMyModel);
}
}
I know ViewModel's shouldn't do business logic themselves
Yes, you're right. ViewModel should not contain business logic but
it should contain UI related logic. So basically, API calls or Some
location related stuffs should be avoided in ViewModel logic.
So what if you wanna make some scenario which can react to any activity lifecycle? I'll suggest you to use LifecycleObserver.
Why?, Because LifecycleObserver will provide you callbacks once it's LifecycleOwner will change it's state.
What is LifecycleOwner here? In our case it may be Activity/Fragment.
So, how you can achieve this?
Let's say you want to make location requests during resume & pause period of any activity.
So, for that you can create a class called LocationUpdates as LifecycleObserver like below:
class LocationUpdates : LifecycleObserver {
constructor(){
// some basic location related initialization here
}
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun connectListener() {
// this method will respond to resume event of our Lifecycle owner (activity/fragment in our case)
// So let's get location here and provide callback
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun disconnectListener() {
// this method will respond to pause event of our Lifecycle owner (activity/fragment in our case)
// So let's stop receiveing location updates here and remove callback
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) // Optional if you want to cleanup references
fun cleanUp() {
// this method will respond to destroy event of our Lifecycle owner (activity/fragment in our case)
// Clean up code here
}
}
Now from your activity, you can directly make your LocationUpdates, and receive callback.
class MyActivity : AppCompatActivity() {
private lateinit var mLocationUpdates: LocationUpdates
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Initialize your LifecycleObserver here & assign it to this activity's lifecycle
lifecycle.addObserver(mLocationUpdates)
}
}
You can refer to how to handle Lifecycle & Codelabs example.
Edit:
If you want to have ViewModel for that job, consider this:
class MyViewModel : ViewModel {
private lateinit var mLocationUpdates: LocationUpdates
constructor() : super() {
// initialize LocationUpdates here
}
// Assign our LifecyclerObserver to LifecycleOwner
fun addLocationUpdates(lifecycle: Lifecycle){
lifecycle.addObserver(mLocationUpdates)
}
//Optional, we really don't need this.
fun removeLocationUpdates(lifecycle: Lifecycle){
lifecycle.removeObserver(mLocationUpdates)
}
}
If your LocationUpdates depends upon Context, consider using AndroidViewModel.
We can now observe our location updates # any activity/fragment using LiveData, and assign our LifecycleObserver like below:
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by lazy {
return#lazy ViewModelProviders.of(this#MyActivity).get(MyViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.addLocationUpdates(lifecycle)
}
}
Please note: there's still lot to cover but making this answer as short as possible. So, if you're still confused about something related then please feel free to ask me in comment. I will edit my answer.
with java 8 LifecycleObserver has been deprecated. According to the [docs][1] it is not recommended to use this class as it uses reflection.
Rather the docs recommend using DefaultLifecycleObserver. To do that, extend your ViewModel class with DefaultLifecycleObserver like:
class MyViewModel : ViewModel(), DefaultLifecycleObserver {//implement default lifecycle observer
override fun onCreate(owner: LifecycleOwner) {//override lifecycle events
super.onCreate(owner)
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
}
}
and get all the lifecycle event callbacks in your viewmodel by registering your viewmodel as lifecycle event observer in your view class (e.g. Activity class) like:
class MyActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
lifecycle.addObserver(splashViewModel)//registering observer
...
}
}
its just and update to the answer by #farid_z with kotlin and new sdk.
[1]: https://developer.android.com/reference/androidx/lifecycle/LifecycleObserver

Android&Kotlin lifecycle events in interface

Is there any possibility to trigger some default methods in Kotlin interfaces with lifecycle events of, for example, an Activity that implements that interface?
So, I have such interface, that called in Swift - protocol:
interface MyInterface {
fun showToast() {
this as MyActivity
Toast.show(this, "Welcome", Toast.LENGTH_SHORT).show()
}
}
And Activity class:
class MyActivity : AppCompatActivity(), MyInterface {
fun onResume() {
super.onResume()
showToast() //I want this method be called automatically, if possible
}
}
As you can see I should call showToast() method directly. But is there any possibility to call it automatically with, for example, LifeCycleObserver events or somehow else?
You can extend LifecycleObserver interface and use appropriate annotations, for example:
interface LifecycleInterface : LifecycleObserver{
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onLifeResume(){
(this as? Context).let{Toast.makeText(it, "Resumed", Toast.LENGTH_LONG).show()}
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onLifePause(){
(this as? Context).let{Toast.makeText(it, "Paused", Toast.LENGTH_LONG).show()}
}
}
Then register activity itself (or any custom object for that matter) as listener for lifecycle events:
class MainActivity : AppCompatActivity(), LifecycleInterface {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(this) // add this to trigger lifecycle methods from interface
setContentView(R.layout.activity_main)
// rest of your onCreate...
}
}
Edit:
After showing bytecode and decompiling back to java, I end up with those two methods injected into activity:
#OnLifecycleEvent(Event.ON_RESUME)
public void onLifeResume() {
DefaultImpls.onLifeResume(this);
}
#OnLifecycleEvent(Event.ON_PAUSE)
public void onLifePause() {
DefaultImpls.onLifePause(this);
}
For anyone still facing issue due to compiler adding a parameter in default function of the interface, this is how you fix it:
Instead of using kapt to process the annotations:
kapt "androidx.lifecycle:lifecycle-compiler:$archLifecycleVersion"
Use annotationProcessor for lifecycler compiler:
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$archLifecycleVersion"
This works with kotlin code.

Android Kotlin Mvp Class delegation

So I have the following scenario:
class NowActivity: AppCompatActivity(), NowScreen, NowDelegate by NowDelegateImpl(){
onCreate(...){
presenter.attachView(this)
}
Is there any way I can delegate the implementation of some NowScreen methods to NowDelegate so I can do the following inside the presenter:
view.callSomeFunc()
in which callSomeFund() is implemented in NowDelegate.
Is there any way of accomplish something like this? the problem is that I'm using MVP, which attach a view to a presenter. But some of the view implementation is repeated in several activities, so I would want to delegate it to another class.
You can delegate both interfaces to the same object if it implements both interfaces. To do so just make the object a constructor parameter, for example:
class NowActivity(delegate: NowDelegateImpl): AppCompatActivity(),
NowScreen by delegate,
NowDelegate by delegate {
constructor (): this(NowDelegateImpl()) {} // need this default constructor for Android to call
...
}
If the delegate does not implement everything of both interfaces, you can make it a member and manually delegate some subset of the functions to it.
class NowActivity(private val delegate: NowDelegateImpl):
AppCompatActivity(),
NowScreen,
NowDelegate by delegate {
constructor (): this(NowDelegateImpl()) {} // need this default constructor for Android to call
override fun callSomeFund() { delegate.callSomeFund() }
}
Both options need you to create a default constructor that creates the object used for delegation and passes that to the primary constructor.
Here it is broken out to an all inclusive sample that isn't so Android specific in case others want to see all that is going on...
Example 1, delegate all interfaces to same object:
interface CommonStuff {
fun foo1()
fun foo2()
}
interface LessCommonStuff {
fun bar()
}
class CommonDelegate1: CommonStuff, LessCommonStuff {
override fun foo1() {}
override fun foo2() {}
override fun bar() {}
}
class Activity1(delegate: CommonDelegate1):
LessCommonStuff by delegate,
CommonStuff by delegate {
constructor (): this(CommonDelegate1()) {} // need this default constructor
// ...
}
Example 2, manually delegate some interfaces using a member:
interface CommonStuff {
fun foo1()
fun foo2()
}
interface LessCommonStuff {
fun bar()
}
class CommonDelegate2: CommonStuff {
override fun foo1() {}
override fun foo2() {}
fun barLikeThing() {}
}
class Activity2(private val delegate: CommonDelegate2):
LessCommonStuff,
CommonStuff by delegate {
constructor (): this(CommonDelegate2()) {} // need this default constructor
override fun bar() { delegate.barLikeThing() }
}

Categories

Resources