Currently i am using MVP pattern on android with Contract.
So in example my interactor is like this:
interface MainInteractor {
interface Activity {
//function here
}
interface Presenter {
//function here
}
}
and my Presenter class contains something like this
class MainPresenter(
var activity : MainInteractor.Activity
) : MainInteractor.Presenter {
//interface function that calls API (async)
fun callNetwork() {
//code here
}
}
In sense i want to know if this kind of pattern will cause memory leak if calling network hasn't finished but the activity has already been destroyed. I know for AsyncTask, weak reference will be used to avoid memory leak. Is it the same case here? And if it does cause memory leak are there any way to fix it aside from weak reference.
You could make method attach and detach. In attach you could initialize something, in detach you could finilize something. For example you could set null to your activity contract interface variable and inside all your callbacks before call method check if not null.
Case with AsyncTask is not similar to your case. With AsyncTask we passed inside View or Activity link, it means after rotate previous activity/view destroyed, but out async task has old link and GC cannot clear memory.
Related
I've seen tutorials where writing to Room performed in a coroutine launched using viewModelScope, so it's cancelled when ViewModel is destroyed. But I want data to be written in any case. So I want to launch this coroutine in a scope that doesn't depend on Activity's lifecycle.
class MyApplication : Application() {
val applicationScope = CoroutineScope(SupervisorJob())
}
Is it true that if I launch a long-running coroutine in ViewModel using this scope ViewModel won't be allowed to be garbage collected until the coroutine is finished even if Activity is destroyed?
True. Launching a long-running coroutine from everywhere will retain objects that it references until it's finished or cancelled.
That's why it's advisable to use viewModelScope when inside ViewModel and lifecycleScope in activities and fragments. Because they know how to cancel those jobs.
https://developer.android.com/topic/libraries/architecture/coroutines
But if you don't reference your activity or context (which is actually the activity itself) they won't leak.
CoroutineScope(SupervisorJob())
.launch {
// activity.getString(R.string.something) // Memory leak!
applicationContext.getString(R.string.something) // Memory leak on applicationContext but not on activity. That's fine.
}
. . .
}
Or at least that's how I'd expect it to work. But there seems to be an unexpected behavior when you do withContext(NonCancellable) inside there. See https://github.com/Kotlin/kotlinx.coroutines/issues/1061.
In short, write it so there's no references to Activity and other objects that reference Activity, as is good practice. Otherwise expect things to leak. I'd say the same if you reference any viewmodel objects inside a coroutine.
I need to save a EditText data into a file when to user exits the activity. I know I have plenty of options depending on how important saving that data for me. But for this particular case I'm ok that I can afford losing it so best-afford is fine.
We could maybe create a background service to decouple it entirely from the host activity. However,I was also thinking of creating a coroutine scope that might outlive the activity (i.e.: a singleton NonUIScope) and call the block like;
override fun onSaveInstanceState(bundle: ...) {
...
mInput = et.text.toString()
}
override fun onDestroy() {
NonUIScope.launch {
Downloader.saveData(mInput)
}
}
There might be slightly different implementations to achieve the same goal of course, for instance, using onSavedInstanceSate() instead of writing it into a file but my point is: if I use the ApplicationScope that I've created, could I potentially leak the activity (even there's no UI reference in the coroutine)?
As long as NonUIScope is not related in any way to your activity and also Downloader class doesn't hold an instance of your activity, it will not leak your activity.
E.g. if you use GlobalScope, the life time of the launched coroutine is limited only by the lifetime of the whole application, not by your activity.
I'm implementing an android application in MVP architecture.
I keep a reference to view inside my presenter and do time-consuming tasks such as loading from network inside my model.
My problem is that in each call inside my presenter which I want to call a method of View, it may happen that view is destroyed already and its reference is set to null inside presenter.
So when I received results from model, before each call like mView.updateUISomehow() in need to add if (mView!=null) since when control reaches to this point it may happen that mView is null.
I want to know are there any methods that I skip all null checking and handle all possible exception of presenter class in a class-wide exception handler.
P.S. I know about MVVM, LiveData and Room. I want to resolve this exact problem :)
BasePresenter<View>{
View view
updateUI(){
if(view != null)
callUI()
}
abstract callUI();
}
Your controller would have the knowledge of updateUI(), you may choose how to handle that
YourPresenter<ThatElusiveview> extends BasePresenter<ThatElusiveEview>{
callUI(){
// hoping this is not directly called from the controller!!
}
}
I faced the same problem when I was using MVP with too many UI update calls which will happen in the real scenarios. Well did the refactor to Jet pack. I understand your dilemma.
I believe it is doable if you provide Presenter with listener to view, so if view gets destroyed, Presenter will hold the communication from the controller toward View.
it does sound like checking the view != null, but you can have enumerations of different type of updates going from presenter to View. which you can put in one place to check and then direct them to respective update method depending on the type of enumeration action.
This will also help reading the code regarding different action that presenter is capable of sending to the View
Is it okay a presenter having Android Handler?
I know presenters should not have any Android related objects,
but I really don't have any clear answer.
Here's the thing,
this presenter runs a disk IO task on another thread,
meanwhile, the activity has to change its view.
These jobs are supposed to be done concurrently.
So I decided to pass the activity's handler as an argument
and let presenter send message to activity like this:
class FooPresenter: FooContract.Presenter {
…
private fun doDiskIOTask(handler: Handler) {
handler.sendEmptyMessage(0)
do_something_on_new_thread_and_join()
handler.sendEmptyMessage(1)
}
…
}
The activity has to know when the task is started and finished both and change view.
Could you tell me if I'm doing it any wrong or the better way?
Passing a Handler to the presenter doesn't sound like a good idea, you wouldn't be able to unit test it. I think a better approach would be to just call a method on the view and then run on UI thread from there. If your view interface is an Activity you can use the very convenient runOnUiThread method, e.g.
class MyActivity : AppCompatActivity, FooContract.View {
override fun emptyMessage0Method(){
runOnUiThread {
// manipulate views here
}
}
}
In mvp we save that reference of an activity in a weak reference.
WeakReference<Activity> view = new WeakReference<Activity>(activity);
If we lose the reference. can we get it back?
If you lose the reference to your Activity it means the activity was garbage collected and it doesn't exist anymore. You can't get back what doesn't exist.
Ex. If this happens because of configuration change it means a new activity was created.
You need a way to attach the newly created view to the same presenter.
If you are looking for libraries to help you, take a look at mosby and nucleus.
I don't think you should be saving a reference to an Activity in MVP at all - doesn't matter if it's hard or weak!
I'm assuming you're storing this reference in the Presenter. To really decouple the layers you should create an interface that describes your View (Activity) and use it instead of the activity.
So you'd do:
public interface LoginView {
displayUsernameError(String error);
displayPasswordError(String error);
openMainScreen();
}
Your Activity should implement the interface from above.
public class LoginActivity implements LoginView {
...
}
In your presenter you'd have:
class LoginPresenter {
private LoginView mView;
public LoginPresenter(LoginView view) {
mView = view;
}
public onLoginButtonClicked(String username, char[] password) {
...
mView.openMainScreen();
}
}
Immediate benefits of doing this:
The different layers are really decoupled. You can change your Activity (say you decide to use Fragments instead) without touching your Presenter.
Your presenter is fully testable using JUnit only! No need to use anything fancy to verify your interactions are correct, just plain Mockito to mock the LoginView.
One other point to note - are you sure you want your Presenter to outlive your View? There are some situations when that can't be avoided, but in most cases they have the same life span - when the View is destroyed the Presenter should be as well.
How did you set the reference in the first place?
You should be setting it with a setter method in Activity's onCreate.
This "setter" method is often called "attach" or "bind".
fun attach(view: View) {
this.view = view
}
So when new Activity is created due to configuration change, it will set itself to the presenter again. Note that you might be dealing with a new instance of the presenter too. However, based on your question I believe you want to attach the newly created activity to the same instance of presenter. If you got your presenter scoped right, this will work for both cases :)