Should ViewModel class contain Android elements? - android

Moving from MVP to MVVM and trying to learn from tutorials on web.
Some of the tutorials state that ViewModel classes should not have any reference to Activity or View(android.view.View) classes.
But in some of the tutorials i've seen Views are used in ViewModel class and Activities to start other Activities using ViewModel.
For example:
import android.arch.lifecycle.ViewModel;
import android.support.annotation.NonNull;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import com.journaldev.androidmvvmbasics.interfaces.LoginResultCallback;
import com.journaldev.androidmvvmbasics.model.User;
public class LoginViewModel extends ViewModel {
private User user;
private LoginResultCallback mDataListener;
LoginViewModel(#NonNull final LoginResultCallback loginDataListener) {
mDataListener = loginDataListener;
user = new User("", "");
}
public TextWatcher getEmailTextWatcher() {
return new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
user.setEmail(editable.toString());
}
};
}
public TextWatcher getPasswordTextWatcher() {
return new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
user.setPassword(editable.toString());
}
};
}
public void onLoginClicked(#NonNull final View view) {
checkDataValidity();
}
private void checkDataValidity() {
if (user.isInputDataValid())
mDataListener.onSuccess("Login was successful");
else {
mDataListener.onError("Email or Password not valid");
}
}
}
Another one with View.OnClickListener
public class PostViewModel extends BaseObservable {
private Context context;
private Post post;
private Boolean isUserPosts;
public PostViewModel(Context context, Post post, boolean isUserPosts) {
this.context = context;
this.post = post;
this.isUserPosts = isUserPosts;
}
public String getPostScore() {
return String.valueOf(post.score) + context.getString(R.string.story_points);
}
public String getPostTitle() {
return post.title;
}
public Spannable getPostAuthor() {
String author = context.getString(R.string.text_post_author, post.by);
SpannableString content = new SpannableString(author);
int index = author.indexOf(post.by);
if (!isUserPosts) content.setSpan(new UnderlineSpan(), index, post.by.length() + index, 0);
return content;
}
public int getCommentsVisibility() {
return post.postType == Post.PostType.STORY && post.kids == null ? View.GONE : View.VISIBLE;
}
public View.OnClickListener onClickPost() {
return new View.OnClickListener() {
#Override
public void onClick(View v) {
Post.PostType postType = post.postType;
if (postType == Post.PostType.JOB || postType == Post.PostType.STORY) {
launchStoryActivity();
} else if (postType == Post.PostType.ASK) {
launchCommentsActivity();
}
}
};
}
public View.OnClickListener onClickAuthor() {
return new View.OnClickListener() {
#Override
public void onClick(View v) {
context.startActivity(UserActivity.getStartIntent(context, post.by));
}
};
}
public View.OnClickListener onClickComments() {
return new View.OnClickListener() {
#Override
public void onClick(View v) {
launchCommentsActivity();
}
};
}
private void launchStoryActivity() {
context.startActivity(ViewStoryActivity.getStartIntent(context, post));
}
private void launchCommentsActivity() {
context.startActivity(CommentsActivity.getStartIntent(context, post));
}
}
Another one with Activity Reference
public class UserProfileViewModel {
/* ------------------------------ Constructor */
private Activity activity;
/* ------------------------------ Constructor */
UserProfileViewModel(#NonNull Activity activity) {
this.activity = activity;
}
/* ------------------------------ Main method */
/**
* On profile image clicked
*
* #param userName name of user
*/
public void onProfileImageClicked(#NonNull String userName) {
Bundle bundle = new Bundle();
bundle.putString("USERNAME", userName);
Intent intent = new Intent(activity, UserDetailActivity.class);
intent.putExtras(bundle);
activity.startActivity(intent);
}
/**
* #param editable editable
* #param userProfileModel the model of user profile
*/
public void userNameTextChange(#NonNull Editable editable,
#NonNull UserProfileModel userProfileModel) {
userProfileModel.setUserName(editable.toString());
Log.e("ViewModel", userProfileModel.getUserName());
}
}
Is it okay for ViewModel class to contain Android and View classes,
isn't this bad for unit testing?
Which class should a custom view model class extend? ViewModel or
BaseObservable/Observable?
Is there any tutorial link that shows simple usage of MVVM and with
only focus on architecture without any Dagger2, LiveData, or RxJava
extensions? I'm only looking for MVVM tutorials for now.

From the documentation:
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
This is because a ViewModel survives configuration changes. Let's say you have an activity and you rotate the device. The activity is killed and a new instance is created. If you put views in the viewmodel, then the activity won't be garbage collected because the views hold the reference to the previous activity. Also, the views themselves will be recreated but you're keeping old views in the viewmodel. Basically don't put any views, context, activity in the viewmodel.
Here's a sample from google: https://github.com/googlesamples/android-sunflower/

Related

Android Paging Library Loads very slowly for the first time and also when Filter is applied

In my Application I am Having 1,46,000 Records,while applying paging Library the Data is Loading after 10 - 15 seconds.While applying Filter on the Loaded Records it takes minimum 5-10 seconds to Load.The Filter opearation is quiet good before Paging Library used in my Project.
#Dao class Looks Like This`
#Dao
public abstract class MftDao
{
#Query("SELECT* FROM transaction_master where AC_NO LIKE :acNo and TXN_ID LIKE :txn_id order by TXN_DATE DESC")
public abstract DataSource.Factory<Integer,TransactionMaster> getTotTransactions(String acNo,String txn_id);
}
ViewModel Looks Like this
public class ReportViewModel extends AndroidViewModel {
private MutableLiveData<TransactionMasterFilter> filter = new MutableLiveData<>();
private LiveData<PagedList<TransactionMaster>> transactionMasterList;
public void setFilter(TransactionMasterFilter filter) {
this.filter.setValue(filter);
}
public ReportViewModel(#NonNull Application application) {
super(application);
repository = new Repository(application);
mftDao = MftDataBase.getInstance(application).getMftDao();
}
public void init() {
transactionMasterList = Transformations.switchMap( filter, new Function<TransactionMasterFilter, LiveData<PagedList<TransactionMaster>>>() {
#Override
public LiveData<PagedList<TransactionMaster>> apply(TransactionMasterFilter input)
{
DataSource.Factory<Integer,TransactionMaster> transactionMasterFactory;
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(150)
.setPageSize(50).build();
transactionMasterFactory = mftDao.getTotTransactions(input.acNo,input.Txn_id);
return new LivePagedListBuilder(transactionMasterFactory,pagedListConfig).build();
}
} );
}
public static class TransactionMasterFilter
{
public String Txn_id;
public String acNo;
public void setTxn_id(String txn_id) {
Txn_id = "%"+txn_id+"%";
}
public void setAcNo(String acNo) {
this.acNo = "%"+acNo+"%";
}
public TransactionMasterFilter(String txn_id, String acNo) {
Txn_id = txn_id;
this.acNo = acNo;
}
}
public LiveData<PagedList<TransactionMaster>> getTransactionMasterList() {
return transactionMasterList;
}
}
Finally Activity is
public class DailyReportActivity extends AppCompatActivity {
ActivityDailyReportBinding dailyReportBinding;
List<TransactionMaster> transactionMasterList = new ArrayList<>();
Repository repository;
ReportViewModel reportViewModel;
ReportViewModel.TransactionMasterFilter transactionMasterFilter = new ReportViewModel.TransactionMasterFilter("%%","%%");
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
dailyReportBinding = DataBindingUtil.setContentView(this, R.layout.activity_daily_report );
reportViewModel = ViewModelProviders.of(this).get( ReportViewModel.class);
reportViewModel.setFilter(transactionMasterFilter);
reportViewModel.init();
dailyReportBinding.recyclerView.setItemAnimator(new DefaultItemAnimator());
dailyReportBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext(), LinearLayoutManager.VERTICAL, false));
final DailyReportAdapter dailyReportAdapter = new DailyReportAdapter();
dailyReportBinding.recyclerView.setAdapter(dailyReportAdapter);
reportViewModel.getTransactionMasterList().observe( this, new Observer<PagedList<TransactionMaster>>() {
#Override
public void onChanged(PagedList<TransactionMaster> transactionMasters) {
Log.e("",String.valueOf(transactionMasters.getLoadedCount()) + ",Tot = "+ transactionMasters.size());
dailyReportAdapter.submitList(transactionMasters);
}
});
dailyReportBinding.acNoAutoCompleteTextView.addTextChangedListener( new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count)
{
String acNo = dailyReportBinding.acNoAutoCompleteTextView.getText().toString();
transactionMasterFilter.setAcNo(acNo);
reportViewModel.setFilter(transactionMasterFilter);
}
#Override
public void afterTextChanged(Editable s) {
}
});
}
}
I tried a lot to find the solution,but i didn't get it.Am I need to add indexes Or The Procedure to observe the LiveData is totally wrong? . Please Give me suggetions
,Thank you for your time

How to mock an EditText in android using Mockito

I am trying to write a Unit Test for a validator of my android Application. The validator accepts as parameter EditText, therefore I need to mock it. However the mocking does not work, forcing the Test to crash on calling the when() method with the exception:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.
My code is:
#RunWith(MockitoJUnitRunner.class)
public class MyUnitTest
{
#Mock
Context mMockContext;
#Test
public void validateIsCorrect() {
final EditText input = Mockito.mock(EditText.class);
when(input.getText()).thenReturn(Editable.Factory.getInstance().newEditable("123"));
...
}
}
The dependencies in build.gradle file are:
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
The method getText() of the EditText is not private or final. What am I doing wrong? Is it possible to mock an EditText this way? How?
When you're running a unit test, you're using a standard JVM context, not Android's context and that's why it's crashing: The Editable.Factory class and it's methods (like getInstance()) are not in the classpath. And they have not been mocked either.
What I'd do is to create a class that implements Editable with a private member to hold a string reference and use it to mock the getText() method.
Something like this:
class MockEditable implements Editable {
private String str;
public MockEditable(String str) {
this.str = str;
}
#Override #NonNull
public String toString() {
return str;
}
#Override
public int length() {
return str.length();
}
#Override
public char charAt(int i) {
return str.charAt(i);
}
#Override
public CharSequence subSequence(int i, int i1) {
return str.subSequence(i, i1);
}
#Override
public Editable replace(int i, int i1, CharSequence charSequence, int i2, int i3) {
return this;
}
#Override
public Editable replace(int i, int i1, CharSequence charSequence) {
return this;
}
#Override
public Editable insert(int i, CharSequence charSequence, int i1, int i2) {
return this;
}
#Override
public Editable insert(int i, CharSequence charSequence) {
return this;
}
#Override
public Editable delete(int i, int i1) {
return this;
}
#Override
public Editable append(CharSequence charSequence) {
return this;
}
#Override
public Editable append(CharSequence charSequence, int i, int i1) {
return this;
}
#Override
public Editable append(char c) {
return this;
}
#Override
public void clear() {
}
#Override
public void clearSpans() {
}
#Override
public void setFilters(InputFilter[] inputFilters) {
}
#Override
public InputFilter[] getFilters() {
return new InputFilter[0];
}
#Override
public void getChars(int i, int i1, char[] chars, int i2) {
}
#Override
public void setSpan(Object o, int i, int i1, int i2) {
}
#Override
public void removeSpan(Object o) {
}
#Override
public <T> T[] getSpans(int i, int i1, Class<T> aClass) {
return null;
}
#Override
public int getSpanStart(Object o) {
return 0;
}
#Override
public int getSpanEnd(Object o) {
return 0;
}
#Override
public int getSpanFlags(Object o) {
return 0;
}
#Override
public int nextSpanTransition(int i, int i1, Class aClass) {
return 0;
}
}
You can then make use of this class
Mockito.when(input.getText()).thenReturn(new MockEditable("123"));
Looking at this from a bit further away; I am asking myself: why does your validator need to know anything about Android specific classes?
What I mean is: I assume that your validator (in the end) has to check the properties of maybe a String, or something alike?
I would thus suggest to focus on separating concerns here:
Create a component that fetches a String from your EditText
Create a validator that works with such strings
Then you don't need any specific mocking for your validator in the first place!
final EditText editText = Mockito.mock(EditText.class);
final ArgumentCaptor<Editable> captor = ArgumentCaptor.forClass(Editable.class);
Mockito.doNothing().when(editText).setText(captor.capture());
Mockito.when(editText.getText()).thenAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocation) {
return captor.getValue();
}
});
What about this guys, it works for me:
Please don't forget to add the MockitoAnnotations.init(this); and also use
#Mock private EditTextView passwordField;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
when(rootView.findViewById(R.id.button_logout)).thenReturn(buttonLogout);
when(rootView.findViewById(R.id.button_unlock)).thenReturn(buttonUnlock);
when(rootView.findViewById(R.id.ScreenLock_PasswordTextField)).thenReturn(passwordField);
when(passwordField.getText()).thenReturn(Editable.Factory.getInstance().newEditable("asd"));
when(application.getPassword()).thenReturn("asd");
sut = new ScreenLockPresenterImpl(application, rootView, screenLockListener,
logoutButtonClickListener);
}
#Test
public void testOnClickWhenOk() {
sut.onClick(null);
verify(passwordField).getText();
verify(screenLockListener).unLock();
}
I think this is what you are looking for:
when(passwordField.getText()).thenReturn(Editable.Factory.getInstance().newEditable("asd"));

How to use Retrofit and Loader class in the fragment

I used Retrofit to receive the json from my RESTful, it is fine. And I tried to implement the Loader class to maker the data loading logic more clear instead of putting it in a onCreateView method to load it, which is not quite a clear logic for loading data. However, I found a bit confused if I tried to use AsyncTaskLoader( which one supposed to receive the data from asynchronous process) for my retrofit. And I stuck in this point. Retrofit is already an asynchronous process and I wonder should I used the asynchronous call or synchronous call in the retrofit inside the AsyncLoader class.
package generic.fragment;
import android.databinding.ViewDataBinding;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import java.util.ArrayList;
import java.util.List;
import generic.adapter.BaseListAdapter;
public abstract class SwipedLoaderListFragment<Bean, Adapter extends BaseListAdapter<Bean, ? extends ViewDataBinding>> extends SwipedListFragment<Bean, Adapter> implements LoaderManager.LoaderCallbacks<List<Bean>> {
public SwipedLoaderListFragment(FragConfig pFragConfig) {
super(pFragConfig);
}
#Override
public List<Bean> loadData(String query) {
List<Bean> list = new ArrayList<>();
return list;
}
#Override
public void refreshing() {
getLoaderManager().restartLoader(0, null, this);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(0, null, this).forceLoad();
}
#Override
public void onLoadFinished(Loader<List<Bean>> loader, List<Bean> data) {
mAdapter.clear();
mAdapter.addAll(data);
}
#Override
public void onLoaderReset(Loader<List<Bean>> loader) {
mAdapter.clear();
}
}
And this is the fragment I used.
public class LocListFragment extends SwipedLoaderListFragment<String, SimpleStringAdapter> {
public LocListFragment() {
super(new FragConfigBuilder(R.layout.swiped_list).setEnableSwipe(false).setFilterable(true).setEnableDivider(true).build());
}
#Override
public void query(String query) {
super.query(query);
mAdapter.filter(query);
}
#Override
public void queryWhenTextChanged(String query) {
super.queryWhenTextChanged(query);
mAdapter.filter(query);
}
#Override
public SimpleStringAdapter initListAdapter() {
return new SimpleStringAdapter(getActivity(), loadData("")) {
#Override
public ListItemStringBinding bind(ListItemStringBinding pBinding, String pS, int pPosition) {
pBinding.setText(pS);
return pBinding;
}
};
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent lIntent = new Intent();
lIntent.putExtra(SCConstants.PARAM_LOC, mAdapter.getItem(position));
getActivity().setResult(Activity.RESULT_OK, lIntent);
getActivity().finish();
}
#Override
public String getHintStr() {
return "Input Location";
}
#Override
public String getSearchTitle() {
return "Location Search";
}
#Override
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
return new AsyncTaskLoader<List<String>>(getActivity()) {
#Override
public List<String> loadInBackground() {
//here will be the retrofit call
return null;
};
}
}
Since the Loader's loadInBackground method is already asynchronous, it would probably be easier to use a synchronous retrofit call (i.e., use execute rather than enqueue).
For your loader to work, you will also need to override onStartLoading. An implementation might look something like this:
public class MyLoader<List<String>> extends AsyncTaskLoader {
List<String> mResult;
#Override
public List<String> loadInBackground() {
mResult = myHttpApi.execute()...
return mResult;
}
#Override
protected void onStartLoading() {
if (mResult != null) {
deliverResult(mResult);
}
if (mResult == null || takeContentChanged()) {
forceLoad();
}
}
}

Common TextWatcher class

I have 8 fragments, each inflating a layout with among others, an EditText wrapped inside a TextInputLayout. In the onCreateView, am implementing
EditText inputTextFrag1 = (EditText)findViewById(R.id.et_frag1);
inputTextFrag1.addTextChangedListener(new MyTextWatcher(inputTextFrag1));
Am also having to implement MyTextWatcher class in each fragment body as below:
private class MyTextWatcher implements TextWatcher {
private View view;
public MyTextWatcher(View view) {
this.view = view;
}
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
saveButton.getBackground().setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
saveButton.setClickable(false);
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
saveButton.getBackground().setColorFilter(null);
switch (view.getId()) {
case R.id.et_frag1:
validateName();
break;
}
}
}
Where validateName();
private boolean validateName() {
if (inputTextFrag1 .getText().toString().trim().isEmpty()) {
mInputLayoutName.setError(getString(R.string.err_msg_name));
requestFocus(inputTextFrag1 );
return false;
} else {
mInputLayoutName.setErrorEnabled(false);
}
return true;
}
Is there a way to have just one MyTextWatcher class somewhere and one validateName() method to be called by each fragment instead of duplicating the same class/method 8 times. Thanks
Is this the correct way to place the TextWatcher class inside a BaseDialogFragment?
public abstract class BaseDialogFragment extends DialogFragment{
private class MyTextWatcher implements TextWatcher {
private View view;
public MyTextWatcher(View view) {
this.view = view;
}
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
}
}
}
What logic goes into the beforeTextChanged and afterTextChanged methods of the TextWatcher?
You can create a BaseFragment that will be extended by your fragments.
Therefore, you can manage your TextWatcher inside this BaseFragment and consequently the fragments which have this heritage, will receive your expected logic.
As in the following example:
BaseFragment.class
public abstract class BaseFragment extends Fragment implements TextWatcher {
EditText editText;
Button button;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//inflate your edit text
...
//inflate your button
...
editText.addTextChangedListener(this);
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//text watcher listener
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//text watcher listener
}
#Override
public void afterTextChanged(Editable s) {
//text watcher listener
}
}
YourFragment.class
public class YourFragment extends BaseFragment {
...
}
No need for duplication. In your current implementation, it seems your MyTextWatcher class is an inner class of another class (probably fragment class). In this way of implementation you can't share it among all fragment classes.
However if you define your MyTextWatcher class as a standalone class, you can then use it for all fragment classes. To do this, you should only be using the variables and class members that have been declared in scope of the class being defined. In your case saveButton variable doesn't belong to MyTextWatcher class (it's accessible from the outer scope), in such cases, you should import them via the constructor method.
private class MyTextWatcher implements TextWatcher {
private View view;
private Button saveButton;
public MyTextWatcher(View view, Button saveButton) {
this.view = view;
this.saveButton = saveButton;
}
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
saveButton.getBackground().setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
saveButton.setClickable(false);
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
saveButton.getBackground().setColorFilter(null);
switch (view.getId()) {
case R.id.et_frag1:
validateName();
break;
}
}
}
You can now instantiate this class 8 times for your 8 fragments.
However, #Bruno Vieira's solution is better (i.e. using a base fragment class).

Calling functions such as startActivity() inside of an enum

I wanted to centralise the creation of DialogFragments used to report errors to the user in order to just have a class to which I pass the error code and have the dialog spawned automagically.
In order to handle multiple errors I am using an enum in which I define the error propreties.
public enum DialogError {
TTS_NOT_INSTALLED {
#Override
public int getTitleResource() {
return R.string.error_tts_not_installed_title;
}
#Override
public int getMessageResource() {
return R.string.error_tts_not_installed_message;
}
#Override
public int getPositiveButtonResource() {
return R.string.error_tts_not_installed_button_positive;
}
#Override
public void onPositiveButtonClick() {
// TODO
}
#Override
public int getNegativeButtonResource() {
return R.string.error_tts_not_installed_button_negative;
}
#Override
public void onNegativeButtonClick() {
// TODO
}
};
public abstract int getTitleResource();
public abstract int getMessageResource();
public abstract int getPositiveButtonResource();
public abstract void onPositiveButtonClick();
public abstract int getNegativeButtonResource();
public abstract void onNegativeButtonClick();
}
Then I have my FragmentDialogError class that I call to create a new Dialog.
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
public class FragmentDialogError
extends DialogFragment {
Context context;
DialogError error;
public FragmentDialogError(Context context, DialogError error) {
this.context = context;
this.error = error;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder
.setTitle(error.getTitleResource())
.setMessage(error.getMessageResource())
.setPositiveButton(error.getPositiveButtonResource(),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
error.onPositiveButtonClick();
}
})
.setNegativeButton(error.getNegativeButtonResource(),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
error.onNegativeButtonClick();
}
});
return builder.create();
}
}
My problem now is that I can't obviously call functions such as startActivity inside of my enum's onPositiveButtonClick() or onNegativeButtonClick().
One soluction would be using a switch() in FragmentDialogError but this way I would split the code between the enum and the class. Another one would be to define in some way the actions that a button press could trigger and let handle them to another class, but I'm looking for a clean and elegant soluction.
How can I implement this in Java keeping the code tidy?
Why not just add a Context to the onClick handlers that you can use to do startActivity?
Also, instead of overriding all the methods in your enum why not use members and a constructor?
public enum DialogError {
TTS_NOT_INSTALLED(
R.string.error_tts_not_installed_title,
R.string.error_tts_not_installed_message,
R.string.error_tts_not_installed_button_positive,
R.string.error_tts_not_installed_button_negative) {
public void onPositiveButtonClick(Context context) {
context.startActivity...
}
#Override
public void onNegativeButtonClick(Context context) {
// TODO
}
};
private final int mTitle;
private final int mMessage;
private final int mPositive;
private final int mNegative;
private DialogError(int title, int message, int positive, int negative) {
mTitle = title;
mMessage = message;
mPositive = positive;
mNegative = negative;
}
public final int getTitleResource() {
return mTitle;
}
public final int getMessageResource() {
return mMessage;
}
public final int getPositiveButtonResource() {
return mPositive;
}
public final int getNegativeButtonResource() {
return mNegative;
}
public abstract void onPositiveButtonClick();
public abstract void onNegativeButtonClick();
}

Categories

Resources