How to use Retrofit and Loader class in the fragment - android

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();
}
}
}

Related

Error with the simple code using Loaders in android

I was trying to refactor my code and move my code from Async Task to a loader. I came to know about the benefits of loaders through the Android Performance video series Loaders Android Performance
I know why Loaders are used and what classes it has and stuff (The theory). However I am unable to grasp the working concept and thus wrote this poor code. Thus I am also not able to debug it.
**EDIT: I was able to make it work but I still think I am calling it in a wrong manner.
new EarthquakeAsyncTaskLoader(this).forceLoad();
If anyone can help me out, on this.........**
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import java.io.IOException;
import java.util.ArrayList;
public class EarthQuakeActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<ArrayList<EarthQuakes>> {
ArrayList<EarthQuakes> earthquakes = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_earth_quake);
getSupportLoaderManager().initLoader(1, null, this);
}// End of onCreate
#Override
public Loader<ArrayList<EarthQuakes>> onCreateLoader(int id, Bundle args) {
**new EarthquakeAsyncTaskLoader(this).forceLoad();**
}
#Override
public void onLoadFinished(Loader<ArrayList<EarthQuakes>> loader, ArrayList<EarthQuakes> data) {
}
#Override
public void onLoaderReset(Loader<ArrayList<EarthQuakes>> loader) {
}
public class EarthquakeAsyncTaskLoader extends AsyncTaskLoader<ArrayList<EarthQuakes>> {
public EarthquakeAsyncTaskLoader(Context context) {
super(context);
}
#Override
protected void onStartLoading() {
// If the data is there, don't start again
if (earthquakes != null) {
deliverResult(earthquakes);
} else {
//Start the loader
forceLoad();
}
}
#Override
public ArrayList<EarthQuakes> loadInBackground() {
// Get the populated list from QueryUtils java class
try {
// Here in QueryUtils, I am making an HTTP network call
// Thus it has to be done in a helper background thread
earthquakes = QueryUtils.getArrayList();
} catch (IOException e) {
e.printStackTrace();
}
return earthquakes;
}
#Override
public void deliverResult(ArrayList<EarthQuakes> data) {
// Feed the adapter with data and display it
ListView earthquakesList = (ListView) findViewById(R.id.listV);
final EarthQuakeAdapter adapter = new EarthQuakeAdapter(getApplicationContext(), data);
earthquakesList.setAdapter(adapter);
earthquakesList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
EarthQuakes currentEarthquake = adapter.getItem(i);
Uri earthquakeUri = Uri.parse(currentEarthquake.getUrl());
// Create a new intent to view the earthquake URI
Intent websiteIntent = new Intent(Intent.ACTION_VIEW, earthquakeUri);
// Send the intent to launch a new activity
startActivity(websiteIntent);
}
});
}
}//End of Async Task Loader
//EarthQuakes is my class. I don't think you'll need this. But anyway:
public class EarthQuakes {
private double mMagnitude;
private String mLocationSmallText;
private String mLocationMainText;
private String mDateOfEarthquake;
private String mUrl;
// Default Constructor
public EarthQuakes(Double mag, String locationSmallText, String locationMainCityName, String dateE, String Url) {
this.mMagnitude = mag;
this.mLocationSmallText = locationSmallText;
this.mLocationMainText = locationMainCityName;
this.mDateOfEarthquake = dateE;
this.mUrl = Url;
}
// Public getters
public Double getMagnitude() {
return mMagnitude;
}
public String getLocationSmallTextEarthquake() {
return mLocationSmallText;
}
public String getLocationLargeTextEarthquake() {
return mLocationMainText;
}
public String getDateOfEarthquake() {
return mDateOfEarthquake;
}
public String getUrl() {
return mUrl;
}
}
This alternative will also work:
getSupportLoaderManager().initLoader(1, null, this).forceload();
However, this is just a way around an issue with loaders that is mentioned here.
This issue happens if you are using AsyncTaskLoader that is not a CursorLoader.
You need to implement onStartLoading() and handle calling forceLoad() there. Highly recommend going through the issue page.
If you are using multiple loaders throughout your app and don't want to implement onStartLoading() every time. Here's a custom loader class you can include in your app and inherit from this instead of the usual AsyncTaskLoader:
WrappedAsyncTaskLoader.java(Original Author: Alexander Blom)
public abstract class WrappedAsyncTaskLoader<D> extends AsyncTaskLoader<D> {
private D mData;
/**
* Constructor of <code>WrappedAsyncTaskLoader</code>
*
* #param context The {#link Context} to use.
*/
public WrappedAsyncTaskLoader(Context context) {
super(context);
}
/**
* {#inheritDoc}
*/
#Override
public void deliverResult(D data) {
if (!isReset()) {
this.mData = data;
super.deliverResult(data);
} else {
// An asynchronous query came in while the loader is stopped
}
}
/**
* {#inheritDoc}
*/
#Override
protected void onStartLoading() {
super.onStartLoading();
if (this.mData != null) {
deliverResult(this.mData);
} else if (takeContentChanged() || this.mData == null) {
forceLoad();
}
}
/**
* {#inheritDoc}
*/
#Override
protected void onStopLoading() {
super.onStopLoading();
// Attempt to cancel the current load task if possible
cancelLoad();
}
/**
* {#inheritDoc}
*/
#Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
this.mData = null;
}
}

Object reference changes when called from onPropertyChanged(DataBinding Android)

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

Android get an error when i use simple class extended AsyncTaskLoader into LoaderManager

I'm trying to use Loaders into android and i'm newbie into this feature, i created simple class with extended from AsyncTaskLoader as this code:
public class StringLoader extends AsyncTaskLoader<List<RobotViewModel>> {
public StringLoader(Context context) {
super(context);
}
#Override
protected void onStartLoading() {
forceLoad();
}
#Override
public List<RobotViewModel> loadInBackground() {
List<RobotViewModel> model = new ArrayList<>();
RobotViewModel temp = new RobotViewModel();
temp.setMessage("hello");
temp.setMessageType(SV.RobotMessageType.SENT_BY_ROBOT.ordinal());
model.add(temp);
return model;
}
#Override
public void deliverResult(List<RobotViewModel> data) {
super.deliverResult(data);
}
}
now i want to use this class into LoaderManager in Activity
private LoaderManager.LoaderCallbacks<List<RobotViewModel>> loaderCallbacks = new LoaderManager.LoaderCallbacks<List<RobotViewModel>>() {
#Override
public Loader<List<RobotViewModel>> onCreateLoader(int id, Bundle args) {
return new StringLoader(getApplicationContext());
}
#Override
public void onLoadFinished(Loader<List<RobotViewModel>> loader, List<RobotViewModel> data) {
adapter.setData(data);
}
#Override
public void onLoaderReset(Loader<List<RobotViewModel>> loader) {
adapter.setData(Collections.<RobotViewModel>emptyList());
}
};
unfortunately i get error on this line of code:
return new StringLoader(getApplicationContext());

Call a method in the calling class on API success in android

I use the following class to make an API call in android using Retrofit
public Class Checkin {
public static void checkinViaApi(CheckinSendModel checkinSendModel) {
final ApiHandler apiHandler = new ApiHandler();
apiHandler.setApiResponseListener(new ApiResponseListener() {
#Override
public void onApiResponse(ApiResponseModel apiResponse) {
Log.i("CheckedIn","true");
}
#Override
public void onApiException(Error error) {
Log.i("fail",error.getErrorMessage());
}
});
List<CheckinSendModel> checkinSendModelList = new ArrayList<CheckinSendModel>();
checkinSendModelList.add(checkinSendModel);
Call<ApiResponseModel> request = RetrofitRestClient.getInstance().checkinToMainEvent(checkinSendModelList,Constant.API_KEY);
apiHandler.getData(request);
}
}
I call that method as follows:
Checkin.checkinViaApi(checkinSendModelObject);
Now, when the API call is successful, I want to execute a function checkedInSuccessfully() in the class from where I make the call. How can I do it?
Thanks in advance
Pass in the response interface.
public class Checkin {
public static void checkinViaApi(CheckinSendModel checkinSendModel, ApiResponseListener listener) {
final ApiHandler apiHandler = new ApiHandler();
apiHandler.setApiResponseListener(listener);
Other class - Call that method
CheckinSendModel model;
Checkin.checkinViaApi(model, new ApiResponseListener() {
#Override
public void onApiResponse(ApiResponseModel apiResponse) {
Log.i("CheckedIn","true");
checkedInSuccessfully();
}
#Override
public void onApiException(Error error) {
Log.i("fail",error.getErrorMessage());
}
);
Interface is your handy man. Create an interface like below.
Interface CheckInListener {
void onCheckIn();
}
Change the checkinViaApi() to below signature.
public static void checkinViaApi(CheckinSendModel checkinSendModel, CheckinListener listener) {
#Override
public void onApiResponse(ApiResponseModel apiResponse) {
Log.i("CheckedIn","true");
listener.onCheckIn();
}
}
When you call the above function you can provide an instance of the interface.
Checkin.checkinViaApi(checkinSendModelObject, new CheckInListener() {
#Override
void onCheckIn() {
//Do your action here
}
});

Adding Functionality to both Android's Activity (parent class) and FragmentActivity (child class) in best coding style (minimal duplication)

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();
}
}

Categories

Resources