So I implemened a custom class that inherits DialogFragment. I want to use this dialog for two types of operations: delete and edit. For that I customized it so for each one I set the title, description and button text. What I want to do is to create an interface and have two presenters: DeletePresenter and EditPresenter that implements that interface. The interface shoul have the click action and the setting of texts for dialog. Being very new to this I can't figure it out how do I connect all of them (dialogFragment, presenters and interface)? If anyone could give me an example that would be great.
MyDialogFragment
public class MyDialogFragment extends DialogFragment implements View.OnClickListener {
private DialogPresenterContract dialogPresenterContract;
private String title, description, buttonTxt;
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.dialog_btn:
dialogPresenterContract.onActionClick(editText.getText().toString().trim(), onDialogActionListener);
break;
}
}
DialogPresenterContract
public interface DialogPresenterContract {
void onActionClick(String reason, MyDialogFragment.OnDialogActionListener onDialogActionListener);
String getDialogTitle();
String getDialogDescription();
String getDialogButtonTxt();
}
DeletePresenter
public class DeletePresenter implements DialogPresenterContract {
//implement all methods
}
EditPresenter
public class EditPresenter implements DialogPresenterContract {
//implement all methods
}
If you want MVP try my sample below, but if you have not match logic I recommend write her right in your view(MyDialogFragment).
public class MyDialogFragment extends DialogFragment implements View.OnClickListener, MyView {
public static MyDialogFragment newInstance(int num) {
MyDialogFragment f = new MyDialogFragment();
Bundle args = new Bundle();
args.putInt("type", type);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter.setType(getArguments().getInt("type"));
...
}
private Presenter presenter;
private String title, description, buttonTxt;
#override
void setStrings(String title, String description, String buttonTitle) {
......
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.dialog_btn:
dialogPresenterContract.onActionClick(editText.getText().toString().trim());
break;
}
}
}
MyView interface
interface MyView {
void setStrings(String title, String description, String buttonTitle);
}
Presenter
class Presenter {
private MyView view;
private int type;
public Presenter(MyView view) {
this.view = view;
}
void setType(int type) {
this.type = type;
}
void onClick(String text) {
if (type == delete_type) {
.....
} else {
......
}
}
}
Related
I have the MainActivity that a want to communicate with a class using an interface.
public interface MyInterface(){
public void doAction();
}
In my MainActivity I will have this piece of code:
public class MainActivity extends AppCompatActivity implements MyInterface(){
//....some more code here
#Override
public void doAction() {
//any code action here
}
//....some more code here
}
So now, If I have another class (NOT ACTIVITY), how should I correctly make the link between class---interface---mainActivity??
public class ClassB {
private MyInterface myinterface;
//........
//...... how to initialize the interface
}
I am confused about how to initialize and use the interface in ClassB
In the constructor of other class: ClassB, accept interface as argument and pass reference of Activity, hold that object in your Activity.
like so:
public class MainActivity extends AppCompatActivity implements MyInterface()
{
private ClassB ref; // Hold reference of ClassB directly in your activity or use another interface(would be a bit of an overkill)
#Override
public void onCreate (Bundle savedInstanceState) {
// call to super and other stuff....
ref = new ClassB(this); // pass in your activity reference to the ClassB constructor!
}
#Override
public void doAction () {
// any code action here
}
}
public class ClassB
{
private MyInterface myinterface;
public ClassB(MyInterface interface)
{
myinterface = interface ;
}
// Ideally, your interface should be declared inside ClassB.
public interface MyInterface
{
// interface methods
}
}
FYI, this is also how View and Presenter classes interact in MVP design pattern.
public class MainActivity extends AppCompatActivity implements
MyInterface
{
OnCreate()
{
ClassB classB= new ClassB(this);
}
}
public class ClassB
{
private MyInterface myinterface;
public ClassB(MyInterface myinterface)
{
this.myinterface=myinterface;
}
void anyEvent() // like user click
{
myinterface.doAction();
}
}
public class MainActivity extends AppCompatActivity implements MyInterface(){
private ClassB ref;
#Override
public void onCreate (Bundle savedInstanceState) {
ref = new ClassB();
ref.setMyinterface(this);
}
#Override
public void doAction () {
// any code action here
}
}
public class ClassB{
private MyInterface myinterface;
public setMyInterface(MyInterface interface){
myinterfece = interface;
}
public interface MyInterface{
// interface methods
}
}
//-------------------------------------
//Two way communication using Interface
//-------------------------------------
//A. Interfaces
//Communicator Interface ( Activity to Fragment )
public interface CommunicateToFragment {
public void CallBack(String name);
}
// Communicator Interface ( Fragment to Main )
public interface CommunicateToMain {
public void respond(String data);
}
//B. Main Class implements CommunicateToMain Interface
//Use CommunicateToFragment interface to send data in FragmentA
public class MainActivity extends AppCompatActivity implements CommunicateToMain {
private CommunicateToFragment communicateToFragment;
public void setCommunicateToFragment(CommunicateToFragment communicateToFragment) {
this.communicateToFragment = communicateToFragment;
}
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public void respond (String data) {
communicateToFragment.CallBack("Callbacked when onCreate method Created" + data);
Log.d("test","get result from fragment: " + data);
FragmentManager manager = getFragmentManager();
FragmentB f2 = (FragmentB) manager.findFragmentById(R.id.id_fragment2);
f2.changeText(data);
}
}
//C. FragmentA implements CommunicateToFragment
//Use CommunicateToMain interface to send data in MainActivity
public class FragmentA extends Fragment implements View.OnClickListener, CommunicateToFragment{
Button button;
int counter=0;
CommunicateToMain commToMain;
#Nullable
#Override
public View onCreateView (LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_a,container,false);
}
#Override
public void onActivityCreated (#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
commToMain = (CommunicateToMain) getActivity();
button = getActivity().findViewById(R.id.button);
button.setOnClickListener(this);
if(getActivity() instanceof MainActivity){
((MainActivity) getActivity()).setCommunicateToFragment(this);
}
}
#Override
public void onClick (View view) {
counter++;
commToMain.respond("The button was clicked" + counter + " Times");
}
#Override
public void CallBack(String name) {
Log.d("test","get result from main activity: " + name);
}
}
I am reinventing my app using a classic MVP approach. In order to to this I read many many articles and tutorials, and what I came out with is that the best way is to :
create an interface for the presenter and one for the view
make fragments and activities implements view interfaces
create an implementation of the presenter interface, which takes in the constructor an instance of the the view it manages, and hold a reference to the presenter inside the view's implementation.
So I have created this classes
VIEW INTERFACE
public interface SignupEmailView extends BaseView {
void fillEmail(String email);
void onEmailInvalid(String error);
void onDataValidated();
}
PRESENTER INTERFACE
public interface SignupEmailPresenter {
void initData(Bundle bundle);
void validateData(String email);
}
VIEW IMPLEMENTATION
public class FrSignup_email extends BaseSignupFragmentMVP implements IBackHandler, SignupEmailView {
public static String PARAM_EMAIL = "param_email";
#Bind(R.id.signup_step2_new_scrollview)
ScrollView mScrollview;
#Bind(R.id.signup_step2_new_lblTitle)
SuperLabel mLblTitle;
#Bind(R.id.signup_step2_new_lblSubtitle)
TextView mLblSubtitle;
#Bind(R.id.signup_step2_new_txtEmail)
EditText mTxtEmail;
#Bind(R.id.signup_step2_new_btnNext)
Button mBtnNext;
protected SignupActivityView mActivity;
SignupEmailPresenter mPresenter;
public FrSignup_email() {
// Required empty public constructor
}
public static FrSignup_email newInstance(String email) {
FrSignup_email fragment = new FrSignup_email();
Bundle b = new Bundle();
b.putString(PARAM_EMAIL, email);
fragment.setArguments(b);
return fragment;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mActivity = (SignupActivityView) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement IResetPasswordBridge");
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = loadView(inflater, container, savedInstanceState, R.layout.fragment_signup_email);
mPresenter = new SignupEmailPresenterImpl(this);
ButterKnife.bind(this, view);
return view;
}
#Override
public final void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
applyCircularReveal();
mPresenter.initData(this.getArguments());
mTxtEmail.setImeOptions(EditorInfo.IME_ACTION_NEXT);
mTxtEmail.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_NEXT) {
mPresenter.validateData(mTxtEmail.getText().toString());
return true;
}
return false;
}
});
mTxtEmail.setOnTouchListener(new OnTouchCompoundDrawableListener_NEW(mTxtEmail, new OnTouchCompoundDrawableListener_NEW.OnTouchCompoundDrawable() {
#Override
public void onTouch() {
mTxtEmail.setText("");
}
}));
mBtnNext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mPresenter.validateData(mTxtEmail.getText().toString());
}
});
}
#Override
public void fillEmail(String email) {
mTxtEmail.setText(email);
}
#Override
public void onEmailInvalid(String error) {
displayError(error);
}
#Override
public void onDataValidated() {
changeFieldToValid(mTxtEmail);
setEmail(mTxtEmail.getText().toString());
// the activity shows the next fragment
mActivity.onEmailValidated();
}
#Override
public boolean doBack() {
if (!isLoading()) {
mActivity.onEmailBack();
}
return true;
}
#Override
public void displayError(String error) {
changeFieldToInvalid(mTxtEmail);
mLblSubtitle.setText(error);
mLblSubtitle.setTextColor(ContextCompat.getColor(getActivity(), R.color.field_error));
}
}
PRESENTER IMPLEMENTATION
public class SignupEmailPresenterImpl implements SignupEmailPresenter {
private SignupEmailView mView;
public SignupEmailPresenterImpl(SignupEmailView view) {
mView = view;
}
#Override
public void initData(Bundle bundle) {
if (bundle != null) {
mView.fillEmail(bundle.getString(FrSignup_email.PARAM_EMAIL));
}
}
#Override
public void validateData(String password) {
ValidationUtils_NEW.EmailStatus status = ValidationUtils_NEW.validateEmail(password);
if (status != ValidationUtils_NEW.EmailStatus.VALID) {
mView.onEmailInvalid(ValidationUtils_NEW.getEmailErrorMessage(status));
} else {
mView.onDataValidated();
}
}
}
Now the fragment is held by an activity which implements this view interface and has its own presenter
public interface SignupActivityView extends BaseView {
void onEmailValidated();
void onPhoneNumberValidated();
void onPasswordValidated();
void onUnlockCodeValidated();
void onResendCodeClick();
void onEmailBack();
void onPhoneNumberBack();
void onPasswordBack();
void onConfirmCodeBack();
void onSignupRequestSuccess(boolean resendingCode);
void onSignupRequestFailed(String errorMessage);
void onTokenCreationFailed();
void onUnlockSuccess();
void onUnlockError(String errorMessage);
void showTermsAndConditions();
void hideTermsAndConditions();
}
My idea is to have a unit test for each project unit, so for each view and presenter implementation I want a unit test, so I want to unit test my fragment with roboletric, and for example I want to test that if I click the "NEXT" button and the email is correct, the hosting Activity's onEmailValidated()method is called. This is my test class
public class SignupEmailViewTest {
private SignupActivity_NEW mActivity;
private SignupActivity_NEW mSpyActivity;
private FrSignup_email mFragment;
private FrSignup_email mSpyFragment;
private Context mContext;
#Before
public void setUp() {
final Context context = RuntimeEnvironment.application.getApplicationContext();
this.mContext = context;
mActivity = Robolectric.buildActivity(SignupActivity_NEW.class).create().visible().get();
mSpyActivity = spy(mActivity);
mFragment = FrSignup_email.newInstance("");
mSpyFragment =spy(mFragment);
mSpyActivity.getFragmentManager()
.beginTransaction()
.replace(R.id.signupNew_fragmentHolder, mSpyFragment)
.commit();
mSpyActivity.getFragmentManager().executePendingTransactions();
}
#Test
public void testEmailValidation() {
assertTrue(mSpyActivity.findViewById(R.id.signup_step2_new_lblTitle).isShown());
assertTrue(mSpyActivity.findViewById(R.id.signup_step2_new_lblSubtitle).isShown());
mSpyActivity.findViewById(R.id.signup_step2_new_btnNext).performClick();
assertTrue(((SuperLabel) mSpyActivity.findViewById(R.id.signup_step2_new_lblSubtitle)).getText().equals(mContext.getString(R.string.email_empty)));
((EditText) mSpyActivity.findViewById(R.id.signup_step2_new_txtEmail)).setText("aaa#bbb.ccc");
mSpyActivity.findViewById(R.id.signup_step2_new_btnNext).performClick();
verify(mSpyFragment).onDataValidated();
verify(mSpyActivity).onEmailValidated();
}
}
everything works well, is just the last verify which doesn't work. Note that the previous verify works, so onEmailValidated is called for sure.
Aside from this specific case, I have some point to discuss:
If with roboeletric I am forced to use an activity to instantiate a fragment, how can I test the fragment in complete isolation (which would be the unit tests goal)? I mean, if I use Robolectric.setupActivity(MyActivity.class) and the activity instantiates somewhere a fragment, it will load the activity and the fragment, which is good, but what if the activity manages a flow of fragments? How can I test the second or third fragment without manually navigating to it? Someone can say to use a dummy activity and use FragmentTestUtil.startFragment, but what in the fragment's onAttach() method is implemented the bridging with the parent activity? Is it me going on the wrong way or are this problems still unsolved?
thanks
Actually you don't even require Roboelectric to do any of those tests.
If each fragment/activity implements a different view interface you could implement fake views and instantiate those instead of the activity/fragment. In this way you could have isolated tests.
If you don't want to implement all the methods of the view interface you could use Mockito and stub only the ones that your unit test requires.
Let me know if you need sample code.
I would like to try and use the MVP pattern for a simple Android application I am writing. This is my first time using the MVP pattern, as I'm still learning, so please be gentle ;)
I have a fragment that I would like to use in conjunction with 4 different presenters. The problem I have is, how can I pass a different presenter to each instance?
I would like to pass the presenter to a constructor, however when Android recreates the fragment, it will call the default constructor. Does this mean it will no longer hold a reference to the presenter?
If so, how else can I pass in the presenter?
I've included some psuedo code of what I'd like to do below. Please note I've just typed this straight into the browser so there may be some silly mistakes, but hopefully you get a rough idea of what I mean.
My 2 interfaces:
public interface IClickableListPresenter {
ListAdapter createListAdapter();
void onListItemClick(int position);
}
public interface ITabbable {
String getTitle();
Fragment getFragment();
}
2 example presenters:
public class ArtistPresenter implements IClickableListPresenter {
public ListAdapter createListAdapter(){
// Create a ListAdapter containing a list of artists
}
public void onListItemClick(int position){
// Handle the click event
}
}
public class TitlePresenter implements IClickableListPresenter {
public ListAdapter createListAdapter(){
// Create a ListAdapter containing a list of song titles
}
public void onListItemClick(int position){
// Handle the click event in a completely different way
// to the ArtistPresenter
}
}
My fragment:
public class ClickableListFragment extends ListFragment
implements ITabbable {
private IClickableListPresenter presenter;
private String title;
// What can I do instead of this constructor?
public ClickableListFragment(
String title, IClickableListPresenter presenter){
this.title = title;
this.presenter = presenter;
}
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setListAdapter(presenter.createListAdapter());
}
#Override
public void onListItemClick(ListView l, View v, int position, long id){
presenter.onListItemClick(position);
}
public Fragment getFragment(){
return this;
}
public String getTitle(){
return title;
}
}
And finally, the class that instantiates the fragments:
public class TabsPagerAdapter extends FragmentPagerAdapter{
private ITabbable tabs[] = {
new ClickableListFragment("Artist", new ArtistPresenter()),
new ClickableListFragment("Title", new TitlePresenter()),
//...
};
//...
}
You could do something like this
public class TabsPagerAdapter extends FragmentPagerAdapter{
private ITabbable tabs[] = {
ClickableListFragment.newInstance(ClickableListFragment.Type.ARTIST),
ClickableListFragment.newInstance(ClickableListFragment.Type.TITLE),
//...
};
}
In ClickableListFragment
public enum Type {
ARTIST,
TITLE
}
public static MyFragment newInstance(final Type fragmentType) {
final MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putSerializable(TYPE, fragmentType);
fragment.setArguments(args);
return fragment;
}
and in onCreate
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Type tempType = (Type) getArguments().getSerializable(TYPE);
if(tempType == Type.ARTIST){
variable = new ArtistPresenter();
.... what you want with that variable
.
.
.
}
I have a class "HomeActivity", which is as follows:
public class HomeActivity extends FragmentActivity implements OnClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fm = getSupportFragmentManager();
// Create the list fragment and add it as our sole content.
if (fm.findFragmentById(android.R.id.content) == null) {
HomeFragment list = new HomeFragment();
fm.beginTransaction().add(android.R.id.content, list).commit();
}
}
public static class HomeFragment extends Fragment {
webServiceTask = WebServiceTask.getInstance(
getActivity(), Constants.METHOD_NAME_PRODUCTS,
Constants.PRODUCT_NAME, null);
public void Work() {}
}
}
I have another class WebServiceTask, which is as follows:
final public class WebServiceTask extends AsyncTask<String, String, String> {
private WebServiceTask(final Activity activity, final String methodName,
final String productName, final String addInfo[]) {
super();
this.activity = activity;
this.methodName = methodName;
this.productName = productName;
this.addInfo = addInfo;
}
public static WebServiceTask getInstance(final Activity activity,
final String methodName, final String productName,
final String additionalInfo[]) {
webServiceTask = new WebServiceTask(activity, methodName, productName,
additionalInfo);
return webServiceTask;
}
protected void onPostExecute() {
// Here I am trying to call the work() method in HomeFragment, How can I do that?
}
My question is how can i call the work() method in HomeFragment class from onPostExecute().
I would propose making a listener for you task, and invoke its method in post execute. It will geve you a lot more flexibility and control on what you want to deafter the task finishes. Here is sample code I would use:
public class MyTask extend AsyncTask<Void, Void, Boolean> {
public interface MyTaskListener {
void onSuccess();
void onFailure();
void onError(Throwable t);
}
private Throwable error;
private MyTaskListener listener;
public MyTask(MyTaskListener listener) {
this.listener = listener;
}
#Overrride
public Boolean doInBackground(Void... params) {
try {
if (workCompleted()) {
//work completed without error - return true
return Boolean.TRUE;
} else {
//work failed to complete - return false
return Boolean.FALSE;
}
} catch(Exception e) {
//unexpected error happened - remember error and return null
this.error = e;
return null;
}
}
#Override
public void onPostExecute(Boolean result){
if (!isCancelled()) { //you only want to process if task wasn't cancelled
if (this.error != null && result == null) { //we have error, process it
if (listener != null) {
listener.onError(this.error);
}
}
if (Boolean.FALSE.equals(result)) { //we have faile, process it
if (listener != null) {
listener.onFail();
}
}
if (Boolean.TRUE.equals(result)) { //we have success
if (listener != null) {
listener.onSuccess();
}
}
}
}
}
And then, in you activit/fragment/service/ use something like this:
public class MyActivity extends Activity {
private void someInstanceMethod() {/ *do your work here */}
#Override
public void onCreate(Bundle savedInstanceState) {
//setup ui, or do whatever you need
//create MyAsyncTask with proper listener
MyAsyncTask task = new MyAsyncTask(new MyAsyncTask.MyAsyncTaskListener() {
#Override
public void onSuccess() {
//call your instance method here
someInstanceMethod();
}
#Override
public void onFailure() {
//process fail
}
#Override
public void onError() {
//process error
}
});
}
}
This is one method. I don't know if it is the best one:
Make work function as public static void. Call it from Asynctask onpostexecute as
HomeActivity.Work();
Edit:
One more way( again not sure if this is the best way):
If you cant make this work, consider putting your asynctask class inside the home activity class
Well using the FragmentManger findFragmentById() or findFragmentByTag() you can get an instance of the current fragment and call your fragment method.
Create an interface file
public interface AsynAction
{
public void Work();
}
Implements AsynAction in HomeActivity
public class HomeActivity extends FragmentActivity implements OnClickListener,AsyncAction {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fm = getSupportFragmentManager();
// Create the list fragment and add it as our sole content.
if (fm.findFragmentById(android.R.id.content) == null) {
HomeFragment list = new HomeFragment();
fm.beginTransaction().add(android.R.id.content, list).commit();
}
}
public static class HomeFragment extends Fragment {
webServiceTask = WebServiceTask.getInstance(
getActivity(), Constants.METHOD_NAME_PRODUCTS,
Constants.PRODUCT_NAME, null);
#Override
public void Work()
{
}
}
}
Then make changes in you asynctask to receive asyncAction object as reference
final public class WebServiceTask extends AsyncTask<String, String, String> {
private WebServiceTask(final AyscAction asycAction,final Activity activity, final String methodName,
final String productName, final String addInfo[]) {
super();
this.activity = activity;
this.asycAction=asycAction;
this.methodName = methodName;
this.productName = productName;
this.addInfo = addInfo;
}
public static WebServiceTask getInstance(final AyscAction asycAction,final Activity activity,
final String methodName, final String productName,
final String additionalInfo[]) {
webServiceTask = new WebServiceTask(asycAction,activity, methodName, productName,
additionalInfo);
return webServiceTask;
}
protected void onPostExecute() {
// You can call work from here
if(asynAction!=null)
asyncAction.Work();
}
I am trying to create a communication between a custom View and a DialogFragment with an interface/callback.
Custom View:
public MyDraw extends View implements ColorPickerListener
{
public MyDraw(Context context)
{
super(context);
// ...
MyDialogFragment.setColorPickerListener(this);
}
#Override
public void onColorChanged(int color)
{
// ...
}
}
DialogFragment
public MyDialogFragment extends DialogFragment
{
public interface ColorPickerListener
{
public void onColorChanged(int color);
}
ColorPickerListener colorPickerListener;
public static void setColorPickerListener(ColorPickerListener listener)
{
colorPickerListener = listener;
}
// ....
private void colorSelected(int color)
{
colorPickerListener.onColorChanged(color);
}
}
This is working, but I'm not sure if this is Ok. I am afraid of memory leaks, because I am referencing a static method from View to the dialog fragment.
Is there any alternative solution, like getting the activity, the instance or casting to something?
You don't need to call the static setColorPickerListener method. You can find your DialogFragment instance using findFragmentByTag method and then simply call your setColorPickerListener (non-static method).
public void showPickerDialog() {
DialogFragment newFragment = new PickerFragment();
newFragment.show(this.getSupportFragmentManager(), "dialogfrag1");
getSupportFragmentManager().executePendingTransactions();
// getting the fragment
PickerFragment df1 = (PickerFragment) getSupportFragmentManager().findFragmentByTag("dialogfrag1");
if (df1 != null) {
df1.registerListener(this);
}
}