public class FilterFragmentExp extends BaseFragmentFilter implements ListingMVPview{
#Inject PresenterListing presenterExp;
private FilterUtilsModel filterModel;
private ListingModel listingModel,listingModel_tmp;
public static FilterFragmentExp newInstance(FilterUtilsModel filterModel,ListingModel listingModel) {
FilterFragmentExp fragment = new FilterFragmentExp();
Bundle bundle = new Bundle();
bundle.putSerializable("filter",filterModel);
bundle.putSerializable("listing",listingModel);
fragment.setArguments(bundle);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getComponent().inject(this);
presenterExp.attachView(this);
receiveArguments();
filterModel.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
#Override
public void onPropertyChanged(Observable observable, int i) {
presenterExp.initialize(filterModel.getExpMap(),TYPE_EXP);
}
});
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void onDestroy() {
super.onDestroy();
presenterExp.detachView();
}
#Override
protected int getLayout() {
return R.layout.fragment_filter_exp;
}
#Override
protected FilterUtilsModel getFilter() {
}
private void receiveArguments(){
filterModel = (FilterUtilsModel) getArguments().getSerializable("filter");
listingModel = (ListingModel) getArguments().getSerializable("listing");
listingModel_tmp = listingModel;
}
#Override
public void showLoading() {
}
#Override
public void hideLoading() {
}
#Override
public void showRetry() {
}
#Override
public void hideRetry() {
}
#Override
public void showError(String message) {
}
}
When I moved to this fragment for the first time everything works well, but from next time(pressed back and come back again) I am getting a nullpointer exception from this line -
presenterExp.initialize(filterModel.getExpMap(),TYPE_EXP);
I am supposed to get this error only if I wont set any view(MVP view, here its ListingMVPview) to the presenter object(presenterExp), but I already set it on this line
presenterExp.attachView(this);
Here is my Presenter -
public class PresenterListing extends BasePresenter<ListingMVPview> {
private final Context context;
#Inject
public PresenterListing(#ActivityContext Context context) {
this.context = context;
}
#Override
public void attachView(ListingMVPview mvpView) {
super.attachView(mvpView);
}
#Override
public void detachView() {
super.detachView();
}
public void initialize(Map options, String type) {
getMVPView().showLoading();
this.getListing(options,type);
}
....
}
Here in the above code getMVPView() returning null, though I have set MVP view.
I have used Dagger2 for dependency injection and using constructor injection for creating PresenterListing objects and using MVP architecture.
Any clues would be helpful as this problem is happening on when called from onPropertyChanged, if I move the code to some other place(say on some view's onclicklistener) everything works fine
you can add a null judgement
if (null) {...
advise start to use google mvp, good lucky
Related
I have a difficulties with MVP realization using Moxy library. I read Moxy's github pages and checked examples, but any solution doesn't helps me.
In MyFragment the callbacks of MyFragmentView methods not called, but in MyFragmentPresenter getViewState() returns not null.
I mean that
getViewState().showProgressDialog();
getViewState().setAccounts(accountsResponse);
getViewState().hideProgressDialog();
are called in MyPresenter, but in MyFragment
#Override
public void showProgressDialog() {
// some code
}
#Override
public void hideProgressDialog() {
// some code
}
#Override
public void setAccounts(AccountsResponse accounts) {
// some code
}
doesn't called.
Please help, what's wrong?
My code below.
Gradle
compile 'com.arello-mobile:moxy:1.5.5'
compile 'com.arello-mobile:moxy-app-compat:1.5.5'
compile 'com.arello-mobile:moxy-android:1.5.5'
annotationProcessor 'com.arello-mobile:moxy-compiler:1.5.5'
MyFragmentView
#StateStrategyType(AddToEndSingleStrategy.class)
public interface MyFragmentView extends MvpView {
void showProgressDialog();
void hideProgressDialog();
void showTextTestMessage(String s);
void showCouldNotRetrieveAccounts();
}
MyFragmentPresenter
#PerFragment
#InjectViewState
public class MyFragmentPresenter extends MvpPresenter<MyFragmentView> {
private ApiService apiService;
#Inject
public MyFragmentPresenter(ApiService apiService) {
this.apiService = apiService;
}
public void getAccounts() {
getViewState().showProgressDialog();
getCompositeDisposable().add(apiService.getAccounts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(accountsResponse -> {
if (accountsResponse.err_code != 0) {
throw new LogicException(accountsResponse.message);
}
return accountsResponse;
})
.subscribe(
accountsResponse -> {
getViewState().setAccounts(accountsResponse);
getViewState().hideProgressDialog();
},
throwable -> {
getViewState().hideProgressDialog();
getViewState().showCouldNotRetrieveAccounts();
}
)
);
}
}
MyFragment
public class MyFragment extends MvpAppCompatFragment implements MyFragmentView {
#Inject
#InjectPresenter
public MyFragmentPresenter mPresenter;
#ProvidePresenter
public MyFragmentPresenter providePresenter() {
return mPresenter;
}
#Override
public void onActivityCreated (#Nullable Bundle savedInstanceState){
ComponentManager.getInstance().getActivityComponent(getActivity()).inject((MyActivity) getActivity());
super.onActivityCreated(savedInstanceState);
mPresenter.getAccounts();
}
#Override
public void showProgressDialog() {
// some code
}
#Override
public void hideProgressDialog() {
// some code
}
#Override
public void setAccounts(AccountsResponse accounts) {
// some code
}
#Override
public void showCouldNotRetrieveBankAccounts() {
// some code
}
}
**MyFragmentPagerAdapter **
public class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
#Override
public Fragment getItem (int position){
switch (position) {
case 0:
//
case 1:
//
case 2:
return new MyFragment();
}
return null;
}
#Override
public int getCount() {
return 3;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
I think something went wrong with connecting dagger with moxy.
Check the following issue on GitHub:
https://github.com/Arello-Mobile/Moxy/issues/100
Guys solve the same problem there
I am new to Espresso, and trying to write a test on a fragment that makes a Retrofit call when it is instantiated. When the call is received, I want to check the fragment to see if a view exists. I'm using an IdlingResource for the fragment and setting up a listener to be called that transitions the ResourceCallback to idle when the response is received (followed some steps in the implementation here: https://stackoverflow.com/a/30820189/4102823).
My fragment is instantiated when the user logs in and starts up MainActivity. The problem is that I just don't think my IdlingResource is set up correctly, and I don't know what's wrong with it. It is not even constructed until after the fragment is initiated and the call is made, even though I'm registering the IdlingResource in the test's setUp() method before everything else. So, I think the main issue here is how I get the IdlingResource instantiated alongside the fragment when I run the test, not after it. Could the problem be in the #Rule? Does it start MainActivity (which creates a NewsfeedFragment instance) before the test can run on the fragment? If so, how would I use a rule with my fragment instead?
Here is my fragment:
public ProgressListener mProgressListener;
public boolean mIsProgressShown = true;
public interface ProgressListener {
void onProgressShown();
void onProgressDismissed();
}
public void setProgressListener(ProgressListener progressListener) {
mProgressListener = progressListener;
}
public NewsfeedFragment() {
}
public static NewsfeedFragment newInstance() {
return new NewsfeedFragment();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OSUtil.setTranslucentStatusBar(getActivity().getWindow(), this.getContext());
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
mRootView = (SwipeRefreshLayout) inflater.inflate(R.layout.fragment_newsfeed, container, false);
mNewsfeedFramelayout = (FrameLayout) mRootView.findViewById(R.id.newsfeed_framelayout);
mProgressView = (ProgressBar) mRootView.findViewById(R.id.newsfeed_progress);
mPageIndictorView = (FrameLayout) mRootView.findViewById(R.id.page_indicator);
mPageIndicator = (TextView) mRootView.findViewById(R.id.page_indicator_text);
mRootView.setOnRefreshListener(this);
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) mRootView.findViewById(R.id.newsfeed_viewpager);
mPageChangeListener = this;
if (savedInstanceState != null) {
mTabPosition = savedInstanceState.getInt(EXTRA_TAB_POSITION);
mViewPager.setCurrentItem(mTabPosition);
}
fetchNewsFeed();
return mRootView;
}
private void fetchNewsFeed() {
if (NetworkUtils.isConnectedToInternet(getActivity())) {
if (NetworkUtils.getService() != null) {
if (mRootView.isRefreshing()) {
dismissProgress();
} else {
showProgress();
}
showProgress();
Call<Newsfeed> call = NetworkUtils.getService().getNewsfeed();
call.enqueue(new Callback<Newsfeed>() {
#Override
public void onResponse(Call<Newsfeed> call, Response<Newsfeed> response) {
if (response.isSuccessful()) {
dismissProgress();
mNewsfeed = response.body();
FragmentManager fragmentManager = getChildFragmentManager();
mNewsfeedPagerAdapter = new NewsfeedPagerAdapter(fragmentManager, mNewsfeed.getNewsfeedItems());
}
...
private void showProgress() {
// show the progress and notify the listener
if (mProgressListener != null){
setProgressIndicatorVisible(true);
notifyListener(mProgressListener);
}
}
public void dismissProgress() {
// hide the progress and notify the listener
if (mProgressListener != null){
setProgressIndicatorVisible(false);
mIsProgressShown = false;
notifyListener(mProgressListener);
}
}
public boolean isInProgress() {
return mIsProgressShown;
}
private void notifyListener(ProgressListener listener) {
if (listener == null){
return;
}
if (isInProgress()){
listener.onProgressShown();
}
else {
listener.onProgressDismissed();
}
}
Here is the IdlingResource:
public class ProgressIdlingResource implements IdlingResource, NewsfeedFragment.ProgressListener {
private ResourceCallback mResourceCallback;
private NewsfeedFragment mNewsfeedFragment;
public ProgressIdlingResource(NewsfeedFragment fragment) {
mNewsfeedFragment = fragment;
mNewsfeedFragment.setProgressListener(this);
}
#Override
public String getName() {
return "ProgressIdlingResource";
}
#Override
public boolean isIdleNow() {
return !mNewsfeedFragment.mIsProgressShown;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
mResourceCallback = callback;
}
#Override
public void onProgressShown() {
}
#Override
public void onProgressDismissed() {
if (mResourceCallback == null) {
return;
}
//Called when the resource goes from busy to idle.
mResourceCallback.onTransitionToIdle();
}
}
The fragment test:
public class NewsfeedFragmentTest {
#Before
public void setUp() throws Exception {
Espresso.registerIdlingResources(new ProgressIdlingResource((NewsfeedFragment) MainActivity.getCurrentFragment()));
}
#Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
#Test
public void getViewPager() throws Exception {
onView(allOf(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
withId(R.id.newsfeed_viewpager))).check(matches(isDisplayed()));
}
#Test
public void getNewsfeedItems() throws Exception {
onView(withId(R.id.page_indicator)).check(matches(isDisplayed()));
}
}
Retrofit is using OkHttp, and there is a standard way to setup IdlingResource for that matter. Refer to OkHttp IdlingResource
I am trying to develop an app using Mosby and EventBus. First event I want to have is after user login, creating a sticky event so every screen can access Login info at all times.
Based off the Mosby mail sample, I have a BasePresenter like this:
public abstract class EventBusPresenter<V extends MvpView> extends MvpBasePresenter<V> {
private static final java.lang.String TAG = tag(EventBusPresenter.class);
#Inject
protected EventBus mEventBus;
LoginSuccessfulEvent userInfo;
#Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onLoginSuccessful(LoginSuccessfulEvent event) {
MCLog.i(TAG, "Received a login event!");
userInfo = event;
onLoginReceived(event);
}
protected abstract void onLoginReceived(LoginSuccessfulEvent e);
public LoginSuccessfulEvent getUserInfo(){
return userInfo;
}
#Override
public void attachView(V view) {
super.attachView(view);
mEventBus.register(this);
}
#Override
public void detachView(boolean retainInstance) {
super.detachView(retainInstance);
mEventBus.unregister(this);
}
}
When the user logins I use this code:
public void doLogin(String username, String password) {
if (isViewAttached()) {
getView().showLoading();
}
cancelSubscription();
mSubscriber = new Subscriber<AuthenticationResponse>() {
#Override
public void onCompleted() {
if (isViewAttached()) {
getView().loginSuccessful();
}
}
#Override
public void onError(Throwable e) {
if (isViewAttached()) {
getView().showLoginError();
}
}
#Override
public void onNext(AuthenticationResponse authenticationResponse) {
User user = authenticationResponse.getUser();
MCLog.w(TAG, String.format("Login was successful with user: %s", user) );
mEventBus.postSticky(new LoginSuccessfulEvent(user, authenticationResponse.getToken()));
String token = authenticationResponse.getToken();
MCLog.i(TAG, String.format("Token obtained = %s", token));
mSharedPreferences.edit().putString(PreferenceKeys.TOKEN_KEY, token).apply();
}
};
My idea is that for every screen, as soon as its loaded it can retrieve the UserInfo through the EventBus subscription.
The issue is -- this event arrives too son. As per mosby's own BaseFragment class I do this:
public abstract class BaseFragment<V extends MvpView, P extends MvpPresenter<V>> extends MvpFragment<V, P> {
private Unbinder mUnbinder;
#LayoutRes
protected abstract int getLayoutRes();
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
return inflater.inflate(getLayoutRes(), container, false);
}
#Override public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
inject();
super.onViewCreated(view, savedInstanceState);
mUnbinder = ButterKnife.bind(this, view);
}
#Override public void onDestroyView() {
super.onDestroyView();
mUnbinder.unbind();
}
/**
* Inject dependencies
*/
protected void inject() {
}
}
This means that injection arrives before the views are created, so whenever I try to respond to a LoginEvent being received, the UI elements that need updating are not there anymore.
How can I achieve this? Is this even the right way to do it?
Do mUnbinder = ButterKnife.bind(this, view); either in onCreateView() or if you prefer to do that in onViewCreated() the call mUnbinder = ButterKnife.bind(this, view); before calling super.onViewCreated().
Btw. Using an EventBus for that sounds painful. Why not simply having a class like UserMananger where you can get the current authenticated user or null if user is not authenticated. Also, since you are using RxJava it would make sense to include the UserManager in your RxJava chain / stream on each screen.
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.
In Android Library, FragmentActivity extends Activity. I would like to add a few methods, and override some methods, of the original Activity.
import android.app.Activity
public class Activiti extends Activity {
public void myNewMethod() { ... }
}
Because of the original hierarchy, FragmentActivity extends Activity, myNewMethod() should also be present in my library's FragmentActiviti
import android.support.v4.app.FragmentActivity;
public abstract class FragmentActiviti extends FragmentActivity {
public void myNewMethod() { ... }
}
But this will lead to a duplication of code, which i do not want this happens. Is there a way to avoid this duplication?
Edit: Usage scenario
Activiti.java
public abstract class Activiti extends Activity {
private int current_orientation = Configuration.ORIENTATION_UNDEFINED; // ORIENTATION_UNDEFINED = 0
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
current_orientation = this.getResources().getConfiguration().orientation;
}
protected boolean isDevicePortrait() { return current_orientation == Configuration.ORIENTATION_PORTRAIT; }
}
FragmentActiviti.java
public abstract class FragmentActiviti extends FragmentActivity {
/* This onCreate() can be omitted. Just putting here explicitly. */
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
protected void someUtilsForFragments() { /* not used yet */ }
}
E_fragtest_06.java
public class E_fragtest_06 extends FragmentActiviti {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.out.printf(isDevicePortrait()); // this NOT WORK for now
}
}
Edit 2: Try using Util class
i think using the Decorator Class would be the most nicest way to solve this problem (no duplication of code). But the Decorator Pattern is just a bit hard (or impossible) to apply on Android Activity scenario.
i try implementing #hazzik's approach, but i still experience some problems.
ActivityUtil.java
public abstract class ActivityUtil {
private int current_orientation = Configuration.ORIENTATION_UNDEFINED; // ORIENTATION_UNDEFINED = 0
public void onCreate(Activity activity, Bundle savedInstanceState) {
activity.onCreate(savedInstanceState);
current_orientation = activity.getResources().getConfiguration().orientation;
}
public boolean isDevicePortrait() { return current_orientation == Configuration.ORIENTATION_PORTRAIT; }
}
Activiti.java
public class Activiti extends Activity {
private ActivityUtil activityUtil;
#Override
public void onCreate(Bundle savedInstanceState) {
activityUtil.onCreate(this, savedInstanceState);
}
protected boolean isDevicePortrait() { return activityUtil.isDevicePortrait(); }
}
FragmentActiviti.java
public abstract class FragmentActiviti extends FragmentActivity {
private ActivityUtil activityUtil;
#Override
public void onCreate(Bundle savedInstanceState) {
activityUtil.onCreate(this, savedInstanceState);
}
protected boolean isDevicePortrait() { return activityUtil.isDevicePortrait(); }
}
In ActivityUtil.onCreate(), activity.onCreate(savedInstanceState); is causing this compile error:
The method onCreate(Bundle) from the type Activity is not visible.
If i change Activity to Activiti:
public abstract class ActivityUtil {
public void onCreate(Activiti activity, Bundle savedInstanceState) { ... }
...
}
It will lead to another compile error in FragmentActiviti.onCreate()'s activityUtil.onCreate():
The method onCreate(Activiti, Bundle) in the type ActivityUtil is not applicable for the arguments (FragmentActiviti, Bundle)
i understand why those errors occur. But i just don't know how to avoid them.
To thanks all the guys who have been contributing to this question, especially #flup for guiding me about the Decorator Pattern, #hazzik and #donramos for your extensive efforts, i m here posting
My enhanced Android's Activity and FragmentActivity classes.
If you are also developing Android applications, i hope my codes could help you guys in some ways :-)
ActivityCore.java
package xxx.android;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
public final class ActivityCore {
public interface ActivityCallbackInterface {
public void onCreateCallback(Bundle savedInstanceState);
public void onBeforeSaveInstanceState(Bundle outState);
public void onSaveInstanceStateCallback(Bundle outState);
}
private final Activity activity;
/**
* This current_orientation variable should be once set, never changed during the object life-cycle.
* But Activity.getResources() is not yet ready upon the object constructs.
* That's why THIS CLASS is wholly responsible to maintain THIS VARIABLE UNCHANGED.
*/
private int current_orientation = Configuration.ORIENTATION_UNDEFINED; // ORIENTATION_UNDEFINED = 0
public ActivityCore(Activity activity) { this.activity = activity; }
public void onCreate(Bundle savedInstanceState) {
((ActivityCallbackInterface) activity).onCreateCallback(savedInstanceState);
current_orientation = activity.getResources().getConfiguration().orientation;
}
public void onSaveInstanceState(Bundle outState) {
/**
* THIS is the best ever place i have found, to unload unwanted Fragments,
* thus prevent re-creating of un-needed Fragments in the next state of Activity.
* (state e.g. Portrait-to-Landscape, or Landscape-to-Portrait)
*
* The KEY is to do it BEFORE super.onSaveInstanceState()
* (my guess for this reason is, in super.onSaveInstanceState(),
* it saves the layout hierarchy, thus saved the Fragments into the Bundle also.
* Thus restored.
* Note that Fragments NOT IN LAYOUT, having ONLY TAGS, are also restored.)
*/
((ActivityCallbackInterface) activity).onBeforeSaveInstanceState(outState);
((ActivityCallbackInterface) activity).onSaveInstanceStateCallback(outState);
}
public int getCurrentOrientation() { return current_orientation; }
public boolean isDevicePortrait() { return current_orientation == Configuration.ORIENTATION_PORTRAIT; }
public boolean isDeviceLandscape() { return current_orientation == Configuration.ORIENTATION_LANDSCAPE; }
public boolean isNewDevicePortrait() { return activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; }
public boolean isNewDeviceLandscape() { return activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; }
public boolean isPortrait2Landscape() { return isDevicePortrait() && isNewDeviceLandscape(); }
public boolean isLandscape2Portrait() { return isDeviceLandscape() && isNewDevicePortrait(); }
public String describeCurrentOrientation() { return describeOrientation(current_orientation); }
public String getCurrentOrientationTag() { return getOrientationTag(current_orientation); }
public String describeNewOrientation() { return describeOrientation(activity.getResources().getConfiguration().orientation); }
public String getNewOrientationTag() { return getOrientationTag(activity.getResources().getConfiguration().orientation); }
private String describeOrientation(final int orientation) {
switch (orientation) {
case Configuration.ORIENTATION_UNDEFINED: return "ORIENTATION_UNDEFINED"; // 0
case Configuration.ORIENTATION_PORTRAIT: return "ORIENTATION_PORTRAIT"; // 1
case Configuration.ORIENTATION_LANDSCAPE: return "ORIENTATION_LANDSCAPE"; // 2
case Configuration.ORIENTATION_SQUARE: return "ORIENTATION_SQUARE"; // 3
default: return null;
}
}
#SuppressLint("DefaultLocale")
private String getOrientationTag(final int orientation) {
return String.format("[%d:%s]", orientation, describeOrientation(orientation).substring(12, 16).toLowerCase());
}
}
Activity.java
package xxx.android.app;
import xxx.android.ActivityCore;
import xxx.android.ActivityCore.ActivityCallbackInterface;
import android.os.Bundle;
public abstract class Activity extends android.app.Activity implements ActivityCallbackInterface {
private final ActivityCore activityCore;
public Activity() { super(); activityCore = new ActivityCore(this); }
#Override
protected void onCreate(Bundle savedInstanceState) { activityCore.onCreate(savedInstanceState); }
#Override public void onCreateCallback(Bundle savedInstanceState) { super.onCreate(savedInstanceState); }
#Override
public void onBeforeSaveInstanceState(Bundle outState) {} // Optionally: let child class override
#Override
protected void onSaveInstanceState(Bundle outState) { activityCore.onSaveInstanceState(outState); }
#Override public void onSaveInstanceStateCallback(Bundle outState) { super.onSaveInstanceState(outState); }
public final int getCurrentOrientation() { return activityCore.getCurrentOrientation(); }
public final boolean isDevicePortrait() { return activityCore.isDevicePortrait(); }
public final boolean isDeviceLandscape() { return activityCore.isDeviceLandscape(); }
public final boolean isNewDevicePortrait() { return activityCore.isNewDevicePortrait(); }
public final boolean isNewDeviceLandscape() { return activityCore.isNewDeviceLandscape(); }
public final boolean isPortrait2Landscape() { return activityCore.isPortrait2Landscape(); }
public final boolean isLandscape2Portrait() { return activityCore.isLandscape2Portrait(); }
public final String describeCurrentOrientation() { return activityCore.describeCurrentOrientation(); }
public final String getCurrentOrientationTag() { return activityCore.getCurrentOrientationTag(); }
public final String describeNewOrientation() { return activityCore.describeNewOrientation(); }
public final String getNewOrientationTag() { return activityCore.getNewOrientationTag(); }
}
FragmentActivity.java
package xxx.android.support.v4.app;
import xxx.android.ActivityCore;
import xxx.android.ActivityCore.ActivityCallbackInterface;
import android.os.Bundle;
public abstract class FragmentActivity extends android.support.v4.app.FragmentActivity implements ActivityCallbackInterface {
private final ActivityCore activityCore;
public FragmentActivity() { super(); activityCore = new ActivityCore(this); }
#Override
protected void onCreate(Bundle savedInstanceState) { activityCore.onCreate(savedInstanceState); }
#Override public void onCreateCallback(Bundle savedInstanceState) { super.onCreate(savedInstanceState); }
#Override
public void onBeforeSaveInstanceState(Bundle outState) {} // Optionally: let child class override
#Override
protected void onSaveInstanceState(Bundle outState) { activityCore.onSaveInstanceState(outState); }
#Override public void onSaveInstanceStateCallback(Bundle outState) { super.onSaveInstanceState(outState); }
public final int getCurrentOrientation() { return activityCore.getCurrentOrientation(); }
public final boolean isDevicePortrait() { return activityCore.isDevicePortrait(); }
public final boolean isDeviceLandscape() { return activityCore.isDeviceLandscape(); }
public final boolean isNewDevicePortrait() { return activityCore.isNewDevicePortrait(); }
public final boolean isNewDeviceLandscape() { return activityCore.isNewDeviceLandscape(); }
public final boolean isPortrait2Landscape() { return activityCore.isPortrait2Landscape(); }
public final boolean isLandscape2Portrait() { return activityCore.isLandscape2Portrait(); }
public final String describeCurrentOrientation() { return activityCore.describeCurrentOrientation(); }
public final String getCurrentOrientationTag() { return activityCore.getCurrentOrientationTag(); }
public final String describeNewOrientation() { return activityCore.describeNewOrientation(); }
public final String getNewOrientationTag() { return activityCore.getNewOrientationTag(); }
}
Lastly, i really have to thanks you guys are being so so so helpful and keep updating the solving progress with me! You all are the key persons who make stackoverflow a perfect site for programmers. Should you spot any problems in my codes, or any rooms for improvements, please do not hesitate to help me again :-)
Some improvements?
It is because onBeforeSaveInstanceState() is implemented upon usage, all the three classes need to keep abstract. This leads to a duplication of the member variable current_orientation. If current_orientation could be put into class ActivityBase, or grouping it into somewhere else, it would be a lot nicer!
stupid me. i have fixed it :-)
For my point of view the best solution here is to delegate logic to some class, let's call it CustomActivityLogic.
Also you need to create common interface (CustomActivity) for your activities if you want to access some data or methods of activity classes from your logic class.
To call protected virtual overridden methods there are two solutions:
call method of supper from overridden method
make a new method in subclass and call super method from this new method. Call new method from shared logic.
CustomActivity.java
public interface CustomActivity {
void someMethod();
}
Activiti.java
import android.app.Activity
public class Activiti
extends Activity
implements CustomActivity {
private CustomActivityLogic logic = new CustomActivityLogic();
public void someMethod() { /***/ }
public void myNewMethod() { logic.myNewMethod(this); }
#Override
protected void onCreate(Bundle savedInstanceState) {
logic.onCreate(this, savedInstanceState); // call shared logic
super.onCreate(savedInstanceState); // call super
}
}
FragmentActivitii.java
import android.support.v4.app.FragmentActivity;
public class FragmentActivitii
extends FragmentActivity
implements CustomActivity {
private CustomActivityLogic logic = new CustomActivityLogic();
public void someMethod() { /***/ }
public void myNewMethod() { logic.myNewMethod(this); }
#Override
protected void onCreate(Bundle savedInstanceState) {
logic.onCreate(this, savedInstanceState); // call shared logic
super.onCreate(savedInstanceState); // call super
}
}
CustomActivityLogic.java
public class CustomActivityLogic {
public void myNewMethod(CustomActivity activity) { /*...*/ }
public void onCreate(Activity activity, Bundle savedInstanceState) {
/* shared creation logic */
}
}
Approach with making onCreate available to call from outside via CustomActivity interface
CustomActivity.java
public interface CustomActivity {
void someMethod();
void onCreateSuper(Bundle savedInstanceState);
}
Activiti.java
import android.app.Activity
public class Activiti
extends Activity
implements CustomActivity {
private CustomActivityLogic logic = new CustomActivityLogic();
public void someMethod() { /***/ }
public void myNewMethod() { logic.myNewMethod(this); }
#Override
protected void onCreate(Bundle savedInstanceState) {
logic.onCreate(this, savedInstanceState); // call shared logic
}
public void onCreateSuper(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // call super
}
}
FragmentActivitii.java
import android.support.v4.app.FragmentActivity;
public class FragmentActivitii
extends FragmentActivity
implements CustomActivity {
private CustomActivityLogic logic = new CustomActivityLogic();
public void someMethod() { /***/ }
public void myNewMethod() { logic.myNewMethod(this); }
#Override
protected void onCreate(Bundle savedInstanceState) {
logic.onCreate(this, savedInstanceState); // call shared logic
}
public void onCreateSuper(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // call super
}
}
CustomActivityLogic.java
public class CustomActivityLogic {
public void myNewMethod(CustomActivity activity) { /*...*/ }
public void onCreate(CustomActivity activity, Bundle savedInstanceState) {
/* shared creation logic */
activity.onCreateSuper(savedInstanceState); // call-back super
}
}
You wish to add helper methods that help keep track of the orientation. I'd think this is not quite big enough to warrant the creation of a subclass.
Put them in a helper class instead:
public class OrientationHelper {
private Activity activity;
private int current_orientation;
public OrientationHelper(Activity activity){
this.activity = activity;
orientation = Configuration.ORIENTATION_UNDEFINED;
}
public int getNewOrientation() {
return activity.getResources().getConfiguration().orientation;
}
// call this when you wish to update current_orientation
public void updateOrientation() {
current_orientation = getNewOrientation();
}
public int getCurrentOrientation() {
return current_orientation;
}
public boolean isDevicePortrait() {
return current_orientation == Configuration.ORIENTATION_PORTRAIT;
}
public boolean isDeviceLandscape() {
return current_orientation == Configuration.ORIENTATION_LANDSCAPE;
}
public boolean isNewDevicePortrait() {
return getCurrentOrientation() == Configuration.ORIENTATION_PORTRAIT;
}
public boolean isNewDeviceLandscape() {
return getCurrentOrientation() == Configuration.ORIENTATION_LANDSCAPE;
}
public boolean isPortrait2Landscape() {
return isDevicePortrait() && isNewDeviceLandscape();
}
public boolean isLandscape2Portrait() {
return isDeviceLandscape() && isNewDevicePortrait();
}
public String describeCurrentOrientation() {
return describeOrientation(current_orientation);
}
public String describeNewOrientation() {
return describeOrientation(getNewOrientation());
}
private String describeOrientation(int current_orientation) {
switch (current_orientation) {
case Configuration.ORIENTATION_UNDEFINED:
return "ORIENTATION_UNDEFINED";
case Configuration.ORIENTATION_PORTRAIT:
return "ORIENTATION_PORTRAIT";
case Configuration.ORIENTATION_LANDSCAPE:
return "ORIENTATION_LANDSCAPE";
case Configuration.ORIENTATION_SQUARE:
return "ORIENTATION_SQUARE";
default: return null;
}
}
}
In those activities that work with orientation (and only those), you can instantiate the OrientationHelper and call updateOrientation() in select places.
The other bit of code, that organizes the saving of the instance state, I would not put in a different class just so that you can reuse it. Because this is not where one would expect modifications to state saving to occur and therefore it might get overlooked. (It took me a bit of scrolling around to figure out what it's supposed to do.)
I think the most readable way to go about that is to write it out explicitly in each Activity where you use it.
One last thing to consider is that the Sherlock Actionbar already extends Activity. And rightly so, I think. But this means that you'll occasionally run into trouble if you extend Activity too.
How about using the Decorator Pattern? Unfortunately, this will require you to delegate all of the existing methods, or whichever ones are necessary for your purpose.
public class ActivityDecorator extends Activity
{
private Activity RealActivity;
public ActivityDecorator(Activity _realActivity)
{
RealActivity = _realActivity;
}
public void myNewMethod() { ... } // this exposes the added/new functionality
// unfortunately for old functionality you need to delegate
public void oldMethod() { RealActivity.oldMethod(); }
}
However, once you've done this once for the ActivityProxy class, you can construct instances of ActivityDecorator with types that derive Activity such as FragmentActivity in your case. E.g.
ActivityDecorator decorator = new ActivityDecorator(new FragmentActivity());
Your design problem is one of the issues addressed by the upcoming Java 8 virtual extensions. See URL below for more details:
http://java.dzone.com/articles/java-8-virtual-extension
In the meantime, there is no easy way. A decorator class will not work, instead implement a utility class that will be called by both of your classes:
EDITED BASED ON NEW INFO:
/** NOTE: cannot be abstract class **/
public class ActivitiBase {
private int current_orientation = Configuration.ORIENTATION_UNDEFINED; // ORIENTATION_UNDEFINED = 0
private Activity activity;
public void ActivitiBase(Activity activity) {
this.activity = activity;
}
public void onCreate(Bundle savedInstanceState) {
current_orientation = activity.getResources().getConfiguration().orientation;
}
public boolean isDevicePortrait() { return current_orientation ==
Configuration.ORIENTATION_PORTRAIT; }
}
public void myNewMethod() { ... }
}
Activiti class:
public class Activiti extends Activity {
private ActiviBase activitiBase;
public Activiti() {
activitiBase = new ActiviBase(this);
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activitiBase.onCreate(savedInstanceState);
}
public void myNewMethod() {
activitiBase.myNewMethod();
}
}
FrameActiviti class:
public class FrameActiviti extends FrameActivity {
private ActiviBase activitiBase;
public FrameActiviti() {
activitiBase = new ActiviBase(this);
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activitiBase.onCreate(savedInstanceState);
}
public void myNewMethod() {
activitiBase.myNewMethod();
}
}