How to avoid memory leaks due to custom static handler class? - android

I have certain memory leaks happening in my custom handler class ,but not sure how to fix it. checkedout a couple of examples online but nothing is specific to my code so not sure how to go about it :
private val startupCallback = object: RetryCallback(NUMBER, DELAY) {
override fun onRetry(retryCount: Int) {
mySdkApi.applicationStartup(this)
}
override fun onCompleted(): Boolean {
updateStatus(Callback.Status.StartUpSDK)
return true
}
override fun onFailed(e: MyException?) {
updateStatus(Callback.Status.StartUpSDK, "", e)
}
}
Android studio keeps prompting "This handler class should be static or leaks might occur".Any ideas how to go about it?

The Android Studio complaining is pretty reasonable. The problem is that anonymous classes capture reference to the parent class that they were created in.
There are basically two solutions the "not pretty" and the ugly.) Both of them are about WeakReference.
#1 The not pretty solution is to make a class that will take a weak ref
class ApiRetryCallback(activity: Activity): RetryCallback(NUMBER, DELAY) {
private val weakActivity = WeakReference(activity)
override fun onRetry(retryCount: Int) {
weakActivity.get()!!.mySdkApi.applicationStartup(this) //or weakThis.get()? to swallow null cases
}
override fun onCompleted(): Boolean {
weakActivity.get()!!.updateStatus(Callback.Status.StartUpSDK)
return true
}
override fun onFailed(e: MyException?) {
weakActivity.get()!!.updateStatus(Callback.Status.StartUpSDK, "", e)
}
}
In activity:
private val startupCallback = ApiRetryCallback(this) //this is MainActivity here
#2 The ugly solution is based on a fact that lambdas should capture parent reference, only where there is a direct usage of it. So I came up with this substitution and I didn't see strong references in a debugger but you should check that:
private val startupCallback = {
val weakActivity = WeakReference(this#MainActivity)
object : RetryCallback(NUMBER, DELAY) { //returned as last expression
override fun onRetry(retryCount: Int) {
weakActivity.get()!!.mySdkApi.applicationStartup(this) //or weakThis.get()? to swallow null cases
}
//....else methods....
}
}()
Here the lambda will be called immediately and will capture only the weak reference inside the object, also it will return the last expression wich is object.
#3 While I was writing, I came up with a third solution, which is close to #2
private val startupCallback = WeakReference(this).let { //this here is MainActivity
val weakActivity = it //it of let scope wich is WeakReference
object : RetryCallback(NUMBER, DELAY) { //returned as last expression
override fun onRetry(retryCount: Int) {
weakActivity.get()!!.mySdkApi.applicationStartup(this) //or weakThis.get()? to swallow null cases
}
//....else methods....
}
}

Anonymous classes (like yours) are non static. You can replace anonymous class with the normal class (just create class extending RetryCallback) and pass all needed objects as constructor arguments.

Related

Is it possible to call a non-final function in constructor?

I'm developing a huge section of my Android app in Jetpack Compose with the MVVM pattern.
I have a ViewModel father that is extended by all the other ViewModels. There, I have defined an open function which contains the initialization logic of each ViewModel that I need to call every time I enter in a new screen and to call again when something went wrong and the user clicks on the "try again" button.
abstract class MyFatherViewModel(): ViewModel() {
open fun myInitMethod() {}
fun onTryAgainClick() {
myInitMethod()
}
}
class MyScreen1ViewModel(): MyFatherViewModel() {
init {
myInitMethod()
}
override fun myInitMethod() {
super.myInitMethod()
// Do something
}
}
class MyScreen2ViewModel(): MyFatherViewModel() {
init {
myInitMethod()
}
override fun myInitMethod() {
super.myInitMethod()
// Do something
}
}
Is there a way I can call this method in the init function of MyFatherViewModel instead of doing it in all the children ViewModels? If I try to do that, it gives me the "Calling non-final function in constructor" warning and, of course, it doesn't work.
abstract class MyFatherViewModel(): ViewModel() {
open fun myInitMethod() {}
init {
myInitMethod()
}
fun onTryAgainClick() {
myInitMethod()
}
}
Is it possible to call a non-final function in constructor?
Technically yes, but you shouldn't. Kotlin is trying to protect you from problems here. If you call an open function from a constructor, it means you are running code from the child class before the parent class is completely initialized, and before the child class even started initializing. If the child implementation of the open function tries to access properties from the child class, unexpected things may happen. For instance, non-nullable properties could yield null (because not initialized yet), or primitive values could yield their type's default instead of the default value from their initializer:
fun main() {
Child()
}
open class Parent {
init {
initialize()
}
val id = 42
open fun initialize() = println("parent init")
}
class Child : Parent() {
val name = "Bob"
override fun initialize() = println("initializing $name, parent id=$id")
}
This prints the following output:
initializing null, parent id=0
I guess you can see why this is dangerous.
Maybe you should reconsider what you're trying to do with this try-again feature. Maybe a new view model should be instantiated instead (if try-again is to handle crashes, the state of the current view model may actually be bad enough to want to re-create it from scratch anyway).

Kotlin recursive problem when type checking

I have the following code which i think is valid, because the recursion happens as a result of a callback. It's not called directly as a result of the function call. But the compiler seems to think there is a recursion issue
class Model(callBack: CallBack) {
interface CallBack {
fun onSomething()
}
}
class SomeClass {
fun createModel() = Model(callBack)
val callBack = object : Model.CallBack {
override fun onSomething() {
val anotherModel = createModel()
// Use model for something
}
}
}
Type checking has run into a recursive problem. Easiest workaround: specify types of your declarations explicitly
Is there a workaround for this?
EDIT
I also tried changing callBack to a function so that the same instance is not referenced by multiple models, but I get the same error
The recursive problem mentioned is not about function calls, it's about the compiler trying to find out the types of the declaration and it has stuck in a recursive type checking. It wants to find the output type of createModel which depends on the type of val callback and it depends on createModel again. As it says, declare their types to fix the issue.
class Model(callBack: CallBack)
{
interface CallBack {
fun onSomething()
}
}
class SomeClass {
fun createModel() : Model = Model(callBack)
val callBack : Model.CallBack = object : Model.CallBack {
override fun onSomething() {
val anotherModel : Model = createModel()
// Use model for something
}
}
}

Communication between view and ViewModel in MVVM with LiveData

What is a proper way to communicate between the ViewModel and the View, Google architecture components give use LiveData in which the view subscribes to the changes and update itself accordingly, but this communication not suitable for single events, for example show message, show progress, hide progress etc.
There are some hacks like SingleLiveEvent in Googles example but it work only for 1 observer.
Some developers using EventBus but i think it can quickly get out of control when the project grows.
Is there a convenience and correct way to implement it, how do you implement it?
(Java examples welcome too)
Yeah I agree, SingleLiveEvent is a hacky solution and EventBus (in my experience) always lead to trouble.
I found a class called ConsumableValue a while back when reading the Google CodeLabs for Kotlin Coroutines, and I found it to be a good, clean solution that has served me well (ConsumableValue.kt):
class ConsumableValue<T>(private val data: T) {
private var consumed = false
/**
* Process this event, will only be called once
*/
#UiThread
fun handle(block: ConsumableValue<T>.(T) -> Unit) {
val wasConsumed = consumed
consumed = true
if (!wasConsumed) {
this.block(data)
}
}
/**
* Inside a handle lambda, you may call this if you discover that you cannot handle
* the event right now. It will mark the event as available to be handled by another handler.
*/
#UiThread
fun ConsumableValue<T>.markUnhandled() {
consumed = false
}
}
class MyViewModel : ViewModel {
private val _oneShotEvent = MutableLiveData<ConsumableValue<String>>()
val oneShotEvent: LiveData<ConsumableValue<String>>() = _oneShotData
fun fireEvent(msg: String) {
_oneShotEvent.value = ConsumableValue(msg)
}
}
// In Fragment or Activity
viewModel.oneShotEvent.observe(this, Observer { value ->
value?.handle { Log("TAG", "Message:$it")}
})
In short, the handle {...} block will only be called once, so there's no need for clearing the value if you return to a screen.
What about using Kotlin Flow?
I do not believe they have the same behavior that LiveData has where it would alway give you the latest value. Its just a subscription similar to the workaround SingleLiveEvent for LiveData.
Here is a video explaining the difference that I think you will find interesting and answer your questions
https://youtu.be/B8ppnjGPAGE?t=535
try this:
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
And wrapper it into LiveData
class ListViewModel : ViewModel {
private val _navigateToDetails = MutableLiveData<Event<String>>()
val navigateToDetails : LiveData<Event<String>>
get() = _navigateToDetails
fun userClicksOnButton(itemId: String) {
_navigateToDetails.value = Event(itemId) // Trigger the event by setting a new Event as a new value
}
}
And observe
myViewModel.navigateToDetails.observe(this, Observer {
it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
startActivity(DetailsActivity...)
}
})
link reference: Use an Event wrapper
For showing/hiding progress dialogs and showing error messages from a failed network call on loading of the screen, you can use a wrapper that encapsulates the LiveData that the View is observing.
Details about this method are in the addendum to app architecture:
https://developer.android.com/jetpack/docs/guide#addendum
Define a Resource:
data class Resource<out T> constructor(
val state: ResourceState,
val data: T? = null,
val message: String? = null
)
And a ResourceState:
sealed class ResourceState {
object LOADING : ResourceState()
object SUCCESS : ResourceState()
object ERROR : ResourceState()
}
In the ViewModel, define your LiveData with the model wrapped in a Resource:
val exampleLiveData = MutableLiveData<Resource<ExampleModel>>()
Also in the ViewModel, define the method that makes the API call to load the data for the current screen:
fun loadDataForView() = compositeDisposable.add(
exampleUseCase.exampleApiCall()
.doOnSubscribe {
exampleLiveData.setLoading()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
exampleLiveData.setSuccess(it)
},
{
exampleLiveData.setError(it.message)
}
)
)
In the View, set up the Observer on creation:
viewModel.exampleLiveData.observe(this, Observer {
updateResponse(it)
})
Here is the example updateResponse() method, showing/hiding progress, and showing an error if appropriate:
private fun updateResponse(resource: Resource<ExampleModel>?) {
resource?.let {
when (it.state) {
ResourceState.LOADING -> {
showProgress()
}
ResourceState.SUCCESS -> {
hideProgress()
// Use data to populate data on screen
// it.data will have the data of type ExampleModel
}
ResourceState.ERROR -> {
hideProgress()
// Show error message
// it.message will have the error message
}
}
}
}
You can easily achieve this by not using LiveData, and instead using Event-Emitter library that I wrote specifically to solve this problem without relying on LiveData (which is an anti-pattern outlined by Google, and I am not aware of any other relevant alternatives).
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
implementation 'com.github.Zhuinden:event-emitter:1.0.0'
If you also copy the LiveEvent class , then now you can do
private val emitter: EventEmitter<String> = EventEmitter()
val events: EventSource<String> get() = emitter
fun doSomething() {
emitter.emit("hello")
}
And
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = getViewModel<MyViewModel>()
viewModel.events.observe(viewLifecycleOwner) { event ->
// ...
}
}
// inline fun <reified T: ViewModel> Fragment.getViewModel(): T = ViewModelProviders.of(this).get(T::class.java)
For rationale, you can check out my article I wrote to explain why the alternatives aren't as valid approaches.
You can however nowadays also use a Channel(UNLIMITED) and expose it as a flow using asFlow(). That wasn't really applicable back in 2019.

Accessing Twitter REST API only works in mainActivity, but why?

I am really confused and I need your help! This is only my second App and my first time to work with REST API's. I am simply trying to display some User Information like name and profile picture. It is working perfectly fine when I am using the code in the main Activity, but as soon as I am using a different class for it the API call fails and the code is pretty similar, so I do not know any further. Since Twitter uses Retrofit in their own tutorial I am using it as well.
My Class extending TwitterApiClient, the file is also including the Interface for the custom service:
import android.util.Log
import com.twitter.sdk.android.core.*
import com.twitter.sdk.android.core.models.User
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
class MyTwitterApiClient(session: TwitterSession) : TwitterApiClient(session) {
fun getCustomService() : GetUsersShowAPICustomService {
return getService(GetUsersShowAPICustomService::class.java)
}
}
interface GetUsersShowAPICustomService {
#GET("/1.1/users/show.json")
fun show(#Query("user_id") userId: Long) : Call<User>
}
My Method in the MainActivity looks like this:
private fun loadTwitterAPI(userID: Long) {
MyTwitterApiClient(session).getCustomService().show(userID).enqueue(object : Callback<User>() {
override fun success(result: Result<User>?) {
text.text = (
"Name: "+result!!.data.name
+"\nLocation: "+result!!.data.location
+"\nFriends: "+result!!.data.friendsCount
)
Picasso.with(baseContext).load(result!!.data.profileImageUrl).resize(250, 250).into(imageView)
}
override fun failure(exception: TwitterException?) {
}
})
}
This works perfectly fine, but I do not want to have the call itself in my main activity and I created a companion Object in my Class extending the TwitterApi which should simply get called with the TwitterSession as parameter and it should return an object of the class User which contains all the important data.
The companion Object inside the MyTwitterApiClient class looks like this:
companion object {
fun start(session: TwitterSession): User {
val userID = session.userId
var data: User? = null
MyTwitterApiClient(session).getCustomService().show(userID).enqueue(object : Callback<User>() {
override fun success(result: Result<User>?) {
data = result!!.data
}
override fun failure(exception: TwitterException?) {
throw exception!!
}
})
return data!!
}
}
The new Method in the MainActivity looks like this:
private fun loadTwitterAPI(userID: Long) {
val t = MyTwitterApiClient.start(session)
text.text = (
"Name: "+t.name
+"\nLocation: "+t.location
+"\nFriends: "+t.friendsCount
)
Picasso.with(baseContext).load(t.profileImageUrl).resize(250, 250).into(imageView)
}
Through testing, I found out, that neither the success Method nor the failure Method gets called. And I do not understand at all why it does not call any Method and just fails.
If anyone here already worked with something like this or has a Tip for me it would be super helpful!
Greetings
Btw: The error that crashes my app in the end is a NullPointerException as the Success Method is not called and null gets returned in the end.
Pastebin to my files:
MainActivity: https://pastebin.com/hWByYUFT
MyTwitterApiClient: https://pastebin.com/85xH284K
activity_main.xml: https://pastebin.com/vkzbkL81
depencies in build.gradle: https://pastebin.com/CpX7cwkS
Ok, starting from your code:
fun start(session: TwitterSession): User {
val userID = session.userId
var data: User? = null
MyTwitterApiClient(session).getCustomService().show(userID).enqueue(object : Callback<User>() {
override fun success(result: Result<User>?) {
data = result!!.data
}
override fun failure(exception: TwitterException?) {
throw exception!!
}
})
return data!!
}
Here you are returning data as if it was assigned. You TwitterApiClient does asynchronous task and so the data from data = result!!.data wont be read correctly from
text.text = (
"Name: "+t.name
+"\nLocation: "+t.location
+"\nFriends: "+t.friendsCount
)
Because t is null then. Its data is not yet set. It will be, sometime in the futur, in the asynchronous callback success().
Your main issue seems to be with how to work with asynchronous tasks and how to notify results. Plenty of sources about it. LiveData, RxJava, EventBus might all be leads.
BTW, the reason why your code worked in MainActivity was because you were setting the text after the result came (in success()), so t was good to read.
Good luck and happy learning!

Why are non-null type properties of class obtained from WeakReference.get() changing to nullable

I am trying to write a Handler in an Activity that needs a reference to the activity. If I write like this
class MainActivity : AppCompatActivity() {
private val mHandler = MainActivityHandler(this)
class MainActivityHandler(val activity: MainActivity) : Handler() {
override fun handleMessage(msg: Message?) {
when(msg?.what) {
MSG_CHANGE_TEXT -> {
activity.tv_logged.setText(R.string.title_main)
activity.mHandler.sendMessageDelayed(obtainMessage(SOMETHING), 3000)
}
// ...
}
}
}
}
This code compiles and works as expected. But if I try to pass a weak reference to the activity like this
class MainActivity : AppCompatActivity() {
private val mHandler = MainActivityHandler(WeakReference(this))
class MainActivityHandler(val activityRef: WeakReference<MainActivity>) : Handler() {
private val activity
get() = activityRef.get()
override fun handleMessage(msg: Message?) {
when(msg?.what) {
MSG_CHANGE_TEXT -> {
activity?.tv_logged.setText(R.string.title_main)
activity?.mHandler.sendMessageDelayed(obtainMessage(SOMETHING), 3000)
}
// ...
}
}
}
}
Now the compiler complains that tv_logged and mHandler are nullable receiver type and need to be accessed using ?.
I can understand that the val activity: MainAcitivity? inside the handler is nullable because it comes from WeakReference.get() but how come the properties in MainActivity are also nullable?
Its because the return type of activity?.tv_logged is (assuming its a TextView ) , TextView? . In Kotlin docs , where an alternative is proposed to checking null via if condition
Your second option is the safe call operator, written ?.
b?.length
This returns b.length if b is not null, and null otherwise. The type of this expression is Int? .
To perform a certain operation only for non-null values, you can use the safe call operator together with let:
activity?.let{ //access activity via `it`}
I understood this when I read the whole text at https://kotlinlang.org/docs/reference/null-safety.html#safe-calls
This has nothing to do with WeakReference as I suspected. It happens because safe call operator returns a nullable type even when accessing non-nullable type properties. (The doc doesn't really specify this as explicitly and clearly.)

Categories

Resources