How to get Call Back from ViewModel to View in Android - android

I have ViewModel
class MyViewModel extends BaseViewModel{
public void foo(){
// some code or return some boolean
}
}
View Class
class MyView extends View{
private MyViewModel myviewmodel;
public void bindTo(MyViewModel viewModel) {
this.viewModel = viewModel;
context = viewModel.getContext();
validateView();
requestLayout();
}
private validateView(){
//some code
}
}
this bind view method bind with adapter
I want to get call back in Myview class when ever i will validateView will call please suggest me how get call back from Viewmodel method to View in android.

it is best practice to use live data for communicating from viewmodel to your view.
class MyViewModel {
private MutableLiveData<Boolean> state = new MutableLiveData<Boolean>;
public LiveData<Boolean> getState() {
return state;
}
public void foo() {
//bool = value returned of your work
state.setValue(bool);
} }
class Myview extends View {
public void onCreate() {
viewmodel.getState().observe(this, observer); // 'this' is life cycle owner
}
final Observer<Boolean> observer = new Observer<Boolean>() {
#Override
public void onChanged(#Nullable final Boolean state) {
// do your work with returned value
}
}; }
for more details refer to this

Correct Me if i wrong
first you need to make interface class
public interface ViewModelCallback {
void returnCallBack(Boolean mBoolean);
}
then your View class implements that interface class & Override that method
class MyView extends View implements ViewModelCallback
#Override
public void returnCallBack(Boolean mBoolean) {
//here you will retrieve callback
// Do Something
}
Next you just pass a value from your view model
class MyViewModel {
private ViewModelCallback myViewCallBack;
public void foo() {
Boolean yourReturnValue = false;
myViewCallBack.returnCallBack(yourReturnValue);
}
}

Related

Is it fine to create a separate interface for call back function in MVP pattern

I am trying to create an app by using MVP design pattern. This is the first time i am using this pattern, thats the reason i am little concerned that either i am following the pattern correctly or not.
This is what i have done so far. I am not using Dagger2.
Interface
public interface MainActivityMVP {
interface Model{
void sendTokenToServer(MainActivityMVP.Presenter presenter);
}
interface View{
boolean isPnTokenRegistered();
void tokenUpdated();
void tokenFailedToUpdate();
}
interface Presenter{
void tokenUpdatedSuccessfully();
void tokenAlreadyExists();
void detachView();
}
On MainActivity, I have created an instance of Presenter and Model and pass the Model object to Presenter Constructor
MainActivity
public class MainActivity extends BaseActivity implements MainActivityMVP.View {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
mainPresenter= new MainPresenter(this, new MainModel());
mainPresenter.sendFCMTokenToServer();
}
On Presenter I call Model's method to perform operation, and pass presenter reference to it.
Presenter
public class MainPresenter implements MainActivityMVP.Presenter{
MainActivityMVP.View view;
MainActivityMVP.Model model;
public MainPresenter(MainActivityMVP.View view, MainActivityMVP.Model model){
this.view= view;
this.model= model;
}
public void sendFCMTokenToServer() {
model.sendTokenToServer(this);
}
#Override
public void tokenUpdatedSuccessfully() {
view.tokenUpdated();
}
#Override
public void tokenAlreadyExists() {
view.tokenFailedToUpdate();
}
In Model, I create instance of PreferenceManager class that gets data from SharedPreference
public class MainModel implements MainActivityMVP.Model {
PreferencesHelper preferencesHelper;
public MainModel(){
preferencesHelper= new PreferencesHelper();
}
#Override
public void sendTokenToServer(MainActivityMVP.Presenter presenter) {
if (preferencesHelper.getNotificationSettings().isEmpty()) {
//do stuff
presenter.tokenUpdatedSuccessfully();
}
}
Now i have these questions.
Is the above approach of implementing MVP pattern is fine, or i am
missing something here.
Is it fine if i add an other interface for call backs, or passing
Presenter to model is better approach, as i have seen some example
where they pass interactor reference to model.
Is it necessary to create Interactor Class in MVP pattern
Is it fine, and not against MVP rule if i create a separate
interface for Repository,
Developers have different varieties of implementing MVP. Few people use interactors. Its is not compulsory to use interactors in MVP. I will suggest you below since you are in a starting stage.
public interface MainView extends BaseView {
boolean isPnTokenRegistered();
void tokenUpdated();
void tokenFailedToUpdate();
}
Then have your basepresenter be like this
public interface BasePresenter<V extends BaseView> {
void setView(V view);
void destroyView();
void destroy();
}
Now your MainPresenter
public class MainPresenter implements BasePresenter<MainView>{
MainView view;
PreferencesHelper preferencesHelper;
MainPresenter(){
preferencesHelper= new PreferencesHelper();
}
#Override
public void setView(MainView view) {
this.view = view;
}
#Override
public void destroyView() {
this.view = null;
}
#Override
public void destroy() {
}
public void sendFCMTokenToServer() {
//Do whatever you want
}
}
Finally have your activity like this,
public class MainActivity extends BaseActivity implements MainView {
MainPresenter mainPresenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
mainPresenter= new MainPresenter();
mainPresenter.attachView(this)
mainPresenter.sendFCMTokenToServer();
}

Keeping data State ViewModels

So according to android developers: "Architecture Components provides ViewModel helper class for the UI controller that is responsible for preparing data for the UI. ViewModel objects are automatically retained during configuration changes so that data they hold is immediately available to the next activity or fragment instance."
In the code below there is an asynchronous class that gets called in deleteItem function. My question is this: Does ViewModel also handles the asynchronous calls made inside it or will cause memory leaks?
Thank you
public class BorrowedListViewModel extends AndroidViewModel {
private final LiveData<List<BorrowModel>> itemAndPersonList;
private AppDatabase appDatabase;
public BorrowedListViewModel(Application application) {
super(application);
appDatabase = AppDatabase.getDatabase(this.getApplication());
itemAndPersonList = appDatabase.itemAndPersonModel().getAllBorrowedItems();
}
public LiveData<List<BorrowModel>> getItemAndPersonList() {
return itemAndPersonList;
}
public void deleteItem(BorrowModel borrowModel) {
new deleteAsyncTask(appDatabase).execute(borrowModel);
}
private static class deleteAsyncTask extends AsyncTask<BorrowModel, Void, Void> {
private AppDatabase db;
deleteAsyncTask(AppDatabase appDatabase) {
db = appDatabase;
}
#Override
protected Void doInBackground(final BorrowModel... params) {
db.itemAndPersonModel().deleteBorrow(params[0]);
return null;
}
}
}
I would provide an example, probably you need to modify the code.
First you need a live data change and subscribe to that in your view. Then in the controller you post the value telling the subscriber that something appends. This way asynchronously the view would get alerted.
private MutableLiveData<String> databaseLiveData = new MutableLiveData<>();
...
And in the deleteAsyncTask class you can add:
protected void onPostExecute(Void result) {
databaseLiveData.postValue("some data deleted");
}
And in the BorrowedListViewModel class this method to access from the view add this method:
public LiveData<String> getChanger() {
return databaseLiveData;
}
In the view e.g.Activity add this:
private BorrowedListViewModel mBorrowedListViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
BorrowedListViewModel = ViewModelProviders.of(this).get(BorrowedListViewModel.class);
subscribe();
}
private void subscribe() {
final Observer<String> liveDataChange = new Observer<String>() {
#Override
public void onChanged(#Nullable final String message) {
Log.d("Activity", message);
}
};
liveDataChange.getChanger().observe(this, liveDataChange);
}
Hope this help.

Kotlin type mismatch

I have question about generic types, subtypes and mismatching between those. I have specific structure classes and interface. I'll show you and please explain me why the type mismatch occurs.
Let's say I'm preparing my MVP framework and I have following interfaces and classes:
This is highest abstraction
interface Presenter<in V : AbstractView> {
fun attachView(view: V)
fun detachView()
fun onDestory() {
}
}
The abstract class contains specific methods and implementation of Presenter
abstract class AbstractPresenter<V : AbstractView> : Presenter<V>, LifecycleObserver {
private var viewReference: WeakReference<V?>? = null
protected abstract fun onAttached(view: V)
final override fun attachView(view: V) {
viewReference = WeakReference(view)
onAttached(view)
}
final override fun detachView() {
viewReference?.clear()
viewReference = null
onDetached()
}
protected open fun onDetached() {
}
}
Contract
interface DashboardContract {
interface View : AbstractView {
}
abstract class Presenter : AbstractPresenter<View>(){
}
}
and finally
class DashboardPresenter : DashboardContract.Presenter() {
override fun onAttached(view: DashboardContract.View) {
}
}
In terms of AbstractView it looks simpler. There is just interface AbstractView. In contract DashboardContract.View extends AbstractView interface and my DashboardActivity implement this DashboardContract.View interface.
class DashboardActivity : BaseActivity(), DashboardContract.View { ... }
So when I create DashboardPresenter as a property in my DashboardActivity and create method fun getPresenter() : Presenter<AbstractView> then I got Type mismatch error Why? isn't a subtype of Presenter<AbstractView>?
fun getPresenter() : AbstractPresenter<AbstractView> {
return dashboardPresenter // The type is DashboardPresenter
}
Let's take a looka at the Java code:
I'm watching the Java code from decompile Kotlin. I put it below. This is how the Presenter looks like:
public interface Presenter {
void attachView(#NotNull AbstractView var1);
void detachView();
void onDestory();
#Metadata(...)
public static final class DefaultImpls {
public static void onDestory(Presenter $this) {
}
}
}
I thought that If I use generic class in Kotlin I get the generic class in java too. I was wrong.
The AbstractPresenter gives:
public abstract class AbstractPresenter implements Presenter, LifecycleObserver {
private WeakReference viewReference;
protected abstract void onAttached(#NotNull AbstractView var1);
public final void attachView(#NotNull AbstractView view) {
Intrinsics.checkParameterIsNotNull(view, "view");
this.viewReference = new WeakReference(view);
this.onAttached(view);
}
public final void detachView() {
WeakReference var10000 = this.viewReference;
if(this.viewReference != null) {
var10000.clear();
}
this.viewReference = (WeakReference)null;
this.onDetached();
}
protected void onDetached() {
}
public void onDestory() {
DefaultImpls.onDestory(this);
}
}
Contract
public interface DashboardContract {
#Metadata(...)
public interface View extends AbstractView {
}
#Metadata(...)
public abstract static class Presenter extends AbstractPresenter {
}
}
The DashboardPresetner:
public final class DashboardPresenter extends Presenter {
protected void onAttached(#NotNull View view) {
Intrinsics.checkParameterIsNotNull(view, "view");
}
// $FF: synthetic method
// $FF: bridge method
public void onAttached(AbstractView var1) {
this.onAttached((View)var1);
}
}
You have to change the parent of Presenter in DashboardContractto use AbstractView instead of View:
abstract class Presenter : AbstractPresenter<AbstractView>()
I'm not sure why you're not allowed to use View instead, this might be a flaw
in the recursive type checking of Kotlin. It might be interesting to see what the corresponding java code is and continue investigating from that.

How to inject an Activity into an Adapter using dagger2

Android Studio 3.0 Canary 8
I am trying to inject my MainActivity into my Adapter. However, my solution works ok, but I think its a code smell and not the right way to do it.
My adapter snippet looks like this the but I don't like about this is that I have to cast the Activity to MainActivity:
public class RecipeAdapter extends RecyclerView.Adapter<RecipeListViewHolder> {
private List<Recipe> recipeList = Collections.emptyList();
private Map<Integer, RecipeListViewHolderFactory> viewHolderFactories;
private MainActivity mainActivity;
public RecipeAdapter(Activity activity, Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
this.recipeList = new ArrayList<>();
this.viewHolderFactories = viewHolderFactories;
this.mainActivity = (MainActivity)activity;
}
#Override
public RecipeListViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
/* Inject the viewholder */
final RecipeListViewHolder recipeListViewHolder = viewHolderFactories.get(Constants.RECIPE_LIST).createViewHolder(viewGroup);
recipeListViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
/* Using the MainActivity to call a callback listener */
mainActivity.onRecipeItemClick(getRecipe(recipeListViewHolder.getAdapterPosition()));
}
});
return recipeListViewHolder;
}
}
In my Module, I pass the Activity in the module's constructor and pass it to the Adapter.
#Module
public class RecipeListModule {
private Activity activity;
public RecipeListModule() {}
public RecipeListModule(Activity activity) {
this.activity = activity;
}
#RecipeListScope
#Provides
RecipeAdapter providesRecipeAdapter(Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
return new RecipeAdapter(activity, viewHolderFactories);
}
}
In My Application class I create the components and I am using a SubComponent for the adapter. Here I have to pass the Activity which I am not sure is a good idea.
#Override
public void onCreate() {
super.onCreate();
applicationComponent = createApplicationComponent();
recipeListComponent = createRecipeListComponent();
}
public BusbyBakingComponent createApplicationComponent() {
return DaggerBusbyBakingComponent.builder()
.networkModule(new NetworkModule())
.androidModule(new AndroidModule(BusbyBakingApplication.this))
.exoPlayerModule(new ExoPlayerModule())
.build();
}
public RecipeListComponent createRecipeListComponent(Activity activity) {
return recipeListComponent = applicationComponent.add(new RecipeListModule(activity));
}
My Fragment I inject like this:
#Inject RecipeAdapter recipeAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((BusbyBakingApplication)getActivity().getApplication())
.createRecipeListComponent(getActivity())
.inject(this);
}
Even though the above design works, I think it's a code smell as I have to cast the Activity to the MainActivity. The reason I use the Activity as I want to make this module more generic.
Just wondering if there is a better way
=============== UPDATE USING INTERFACE
Interface
public interface RecipeItemClickListener {
void onRecipeItemClick(Recipe recipe);
}
Implementation
public class RecipeItemClickListenerImp implements RecipeItemClickListener {
#Override
public void onRecipeItemClick(Recipe recipe, Context context) {
final Intent intent = Henson.with(context)
.gotoRecipeDetailActivity()
.recipe(recipe)
.build();
context.startActivity(intent);
}
}
In my module, I have the following providers
#Module
public class RecipeListModule {
#RecipeListScope
#Provides
RecipeItemClickListener providesRecipeItemClickListenerImp() {
return new RecipeItemClickListenerImp();
}
#RecipeListScope
#Provides
RecipeAdapter providesRecipeAdapter(RecipeItemClickListener recipeItemClickListener, Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
return new RecipeAdapter(recipeItemClickListener, viewHolderFactories);
}
}
Then I use it through constructor injection in the RecipeAdapter
public class RecipeAdapter extends RecyclerView.Adapter<RecipeListViewHolder> {
private List<Recipe> recipeList = Collections.emptyList();
private Map<Integer, RecipeListViewHolderFactory> viewHolderFactories;
private RecipeItemClickListener recipeItemClickListener;
#Inject /* IS THIS NESSESSARY - AS IT WORKS WITH AND WITHOUT THE #Inject annotation */
public RecipeAdapter(RecipeItemClickListener recipeItemClickListener, Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
this.recipeList = new ArrayList<>();
this.viewHolderFactories = viewHolderFactories;
this.recipeItemClickListener = recipeItemClickListener;
}
#Override
public RecipeListViewHolder onCreateViewHolder(final ViewGroup viewGroup, int i) {
/* Inject the viewholder */
final RecipeListViewHolder recipeListViewHolder = viewHolderFactories.get(Constants.RECIPE_LIST).createViewHolder(viewGroup);
recipeListViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
recipeItemClickListener.onRecipeItemClick(getRecipe(recipeListViewHolder.getAdapterPosition()), viewGroup.getContext());
}
});
return recipeListViewHolder;
}
}
Just one question, is the #Inject annotation need for the constructor in the RecipeAdapter. As it works with or without the #Inject.
Do not pass Activities into Adapters - This is a really bad practice.
Inject only the fields you care about.
In your example: Pass an interface into the adapter to track the item click.
If you need a MainActivity then you should also provide it. Instead of Activity declare MainActivity for your module.
#Module
public class RecipeListModule {
private MainActivity activity;
public RecipeListModule(MainActivity activity) {
this.activity = activity;
}
}
And your Adapter should just request it (Constructor Injection for non Android Framework types!)
#RecipeListScope
class RecipeAdapter {
#Inject
RecipeAdapter(MainActivity activity,
Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
// ...
}
}
If you want your module to use Activity and not MainActivity then you will need to declare an interface as already mentioned. You adapter would then declare the interface as its dependency.
But in some module you will still have to bind that interface to your MainActivity and one module needs to know how to provide the dependency.
// in some abstract module
#Binds MyAdapterInterface(MainActivity activity) // bind the activity to the interface
Addressing the updated part of the question
Just one question, is the #Inject annotation need for the constructor in the RecipeAdapter. As it works with or without the #Inject.
It works without it because you're still not using constructor injection. You're still calling the constructor yourself in providesRecipeAdapter(). As a general rule of thumb—if you want to use Dagger properly—don't ever call new yourself. If you want to use new ask yourself if you could be using constructor injection instead.
The same module you show could be written as follows, making use of #Binds to bind an implementation to the interface, and actually using constructor injection to create the adapter (which is why we don't have to write any method for it! Less code to maintain, less errors, more readable classes)
As you see I don't need to use new myself—Dagger will create the objects for me.
public abstract class RecipeListModule {
#RecipeListScope
#Binds
RecipeItemClickListener providesRecipeClickListener(RecipeItemClickListenerImp listener);
}
Personally I would do the following trick
public class MainActivity extends AppCompatActivity {
private static final String TAG = "__ACTIVITY__";
public static MainActivity get(Context context) {
// noinspection ResourceType
return (MainActivity)context.getSystemService(TAG);
}
#Override
protected Object getSystemService(String name) {
if(TAG.equals(name)) {
return this;
}
return super.getSystemService(name);
}
}
public class RecipeAdapter extends RecyclerView.Adapter<RecipeListViewHolder> {
private List<Recipe> recipeList = Collections.emptyList();
private Map<Integer, RecipeListViewHolderFactory> viewHolderFactories;
public RecipeAdapter(Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
this.recipeList = new ArrayList<>();
this.viewHolderFactories = viewHolderFactories;
}
#Override
public RecipeListViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
/* Inject the viewholder */
final RecipeListViewHolder recipeListViewHolder = viewHolderFactories.get(Constants.RECIPE_LIST).createViewHolder(viewGroup);
recipeListViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
MainActivity mainActivity = MainActivity.get(v.getContext());
if(recipeListViewHolder.getAdapterPosition() != -1) {
mainActivity.onRecipeItemClick(
getRecipe(recipeListViewHolder.getAdapterPosition()));
}
}
});
return recipeListViewHolder;
}
}

adapter-like pattern for not AdapterView class

I need to transmit data from my activity layer to a view (or at least its fragment) that is not a child of AdapterView.
For a ListView, I could do this very easily with its adapter, but I am stuck on how to reproduce this behavior for a non AdapterView widget (for clarity, let's say a TextView).
I don't want to keep a reference to my fragment (or worse, the view) at Activity level.
Any ideas ?
One way to do this is to use java.util.Observable/Observer :
import java.util.Observable;
import java.util.Observer;
public class MyTextView extends View implements Observer{
#Override
public void update(Observable observable, Object data) {
this.setText((String)data);
}
}
Then, you need an Observable class :
import java.util.Observable;
public class MyObservable extends Observable {
public void setText(String text){
notifyObservers(text);
}
}
Activity :
public class MainActivity extends Activity {
TextView tv;
#Override
public void onCreate(Bundle savedInstanceState) {
...
MyObservable mtv = new MyTextView(getApplicationContext());
MyTextViewModel mm = new MyTextViewModel(10);
mm.addObserver(mtv);
mm.setText("test");
// demonstrated in an activity to shorten the sample, but ideally you would
// keep the observer at activity level and manage the view in the fragment
}
}
------------------------------------------------
Another way to do this is through android.database.DataSetObservable to implement a more traditional Adapter like object :
public class CustomAdapter extends DataSetObservable {
String mText;
public String getText() {
return mText;
}
public void setText(String text) {
mText = text;
}
}
You manipulate it like any other adapter at Activity level :
public class MyActivity extends Activity {
private CustomAdapter mCustomAdapter;
#Override
protected void onCreate() {
...
mCustomAdapter = new CustomAdapter();
}
private void initializeFragment (Fragment fragment) {
// this or whatever method you use to setup your fragments
((MyFragment) fragment).setCustomAdapter(mCustomAdapter);
}
private void onDataLoaded (Stg data) {
// callback method invoked when the network thread has finished loading data
mCustomAdapter.setText(data.text);
mCustomAdapter.notifyChanged();
}
Finally, the only thing missing is the link between your fragment and the view :
public class MyFragment extends Fragment {
private CustomAdapter mCustomAdapter;
public setCustomAdapter(CustomAdapter adapter) {
// this method allows to setup the adapter at startup
mCustomAdapter = adapter;
}
protected DataSetObserver mMyViewObserver = new MyObserver();
private class MyObserver extends DataSetObserver {
#Override
public void onChanged() {
mUpdateHandler.sendEmptyMessage(0);
}
}
private Handler mUpdateHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
updateMyView();
}
};
private void updateMyView() {
if (mView == null) {
return;
}
mView.setMainTextViewText(mCustomAdapter.getText());
}
}
And here you have it. Each time you call notifyChanged(), your observer gets called. In return, he invokes the handler that update the view.
Here you have it, leak free, thread safe custom adapter for any kind of view.

Categories

Resources