So I've come to a point where I need to implement an SQLite database for my app.
Following "The Busy Coder's guide to Android Development" I have created a DatabaseHelper class that extends SQLiteOpenHelper.
One of my use cases is to run a query against the database and display the results on a ListView within a Fragment (I use fragments from the support library).
From what I understand, using managedQuery() is not really appropriate and even if it were it isn't recommended due to the fact that some of the logic encapsulated inside this method is actually executed on the main thread, specifically reQuery() which to my understanding is performed when the Activity is restarted.
So I've been trying to get acquainted with the Loader class for the first time, only to see this:
"The only supplied concrete implementation of a Loader is CursorLoader, and that is only for use with a ContentProvider"
My initial thought was to implement my own content provider and perhaps prevent other apps from getting access to it, then I read the following in the ContentProvider documentation via developer.android.com:
"You don't need a provider to use an SQLite database if the use is entirely within your own application."
I've also been playing with this:
https://github.com/commonsguy/cwac-loaderex
Yet I am not familiar with this project, and not sure if it can be used on a production environment.
So, right now all I can think of is creating a bunch of AsyncTask instances within my Fragment and manage their lifecycle appropriately, make sure they're cancelled whenever needed and whatnot.
Are there any other options?
I think implementing content provider is a good idea, no matter that data will not be accessible outside of the application. It provides very modern interface and from my experience makes your application error prone to database locking issues and other db-specific problems.
I've implemented it in my latest project and I was very happy to use it.
You can extend Loader class in order to perform other Async work such as loading directly from your DB.
Here is an example of that
Edit: added A better example of Loader usage.
Finally managed to find the tutorial that really helped me understand how things work.
By extending the loader class you can avoid messing with content observers and its quite easy to implement (at last)
the modifications that need to be taken in your code are
Add implementation of LoaderManager.LoaderCallbacks<D>, where D is your data list (snippet 1)
Create your loader class, copy snippet 2, and add the loading of your data from the DB
Finally call the loaders 1 call for init and then for refreshing the restart call. (snippet 2 & 3)
Snippet 1: How to "link" the loader with your fragment:
public static class AppListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<List<SampleItem>> {
public Loader<List<SampleItem>> onCreateLoader(int id, Bundle args) {
//...
return new SampleLoader (getActivity());
}
public void onLoadFinished(Loader<List<SampleItem>> loader, List<SampleItem> data) {
// ...
mAdapter.setData(data);
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
// ...
}
public void onLoaderReset(Loader<List<SampleItem>> loader) {
// ...
mAdapter.setData(null);
// ...
}
/* ... */
}
Snippet 2: Schema of your custom loader: (I have double commented the observer things as I believe that it is quite difficult to implement it from the beginning and you can simple recall the loader without messing with that automated refreshing)
public class SampleLoader extends AsyncTaskLoader<List<SampleItem>> {
// We hold a reference to the Loader’s data here.
private List<SampleItem> mData;
public SampleLoader(Context ctx) {
// Loaders may be used across multiple Activitys (assuming they aren't
// bound to the LoaderManager), so NEVER hold a reference to the context
// directly. Doing so will cause you to leak an entire Activity's context.
// The superclass constructor will store a reference to the Application
// Context instead, and can be retrieved with a call to getContext().
super(ctx);
}
/****************************************************/
/** (1) A task that performs the asynchronous load **/
/****************************************************/
#Override
public List<SampleItem> loadInBackground() {
// This method is called on a background thread and should generate a
// new set of data to be delivered back to the client.
List<SampleItem> data = new ArrayList<SampleItem>();
// TODO: Perform the query here and add the results to 'data'.
return data;
}
/********************************************************/
/** (2) Deliver the results to the registered listener **/
/********************************************************/
#Override
public void deliverResult(List<SampleItem> data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
releaseResources(data);
return;
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<SampleItem> oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is in a started state, deliver the results to the
// client. The superclass method does this for us.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
/*********************************************************/
/** (3) Implement the Loader’s state-dependent behavior **/
/*********************************************************/
#Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
// Begin monitoring the underlying data source.
////if (mObserver == null) {
////mObserver = new SampleObserver();
// TODO: register the observer
////}
//// takeContentChanged() can still be implemented if you want
//// to mix your refreshing in that mechanism
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
#Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
#Override
protected void onReset() {
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
// The Loader is being reset, so we should stop monitoring for changes.
////if (mObserver != null) {
// TODO: unregister the observer
//// mObserver = null;
////}
}
#Override
public void onCanceled(List<SampleItem> data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
private void releaseResources(List<SampleItem> data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
/*********************************************************************/
/** (4) Observer which receives notifications when the data changes **/
/*********************************************************************/
// NOTE: Implementing an observer is outside the scope of this post (this example
// uses a made-up "SampleObserver" to illustrate when/where the observer should
// be initialized).
// The observer could be anything so long as it is able to detect content changes
// and report them to the loader with a call to onContentChanged(). For example,
// if you were writing a Loader which loads a list of all installed applications
// on the device, the observer could be a BroadcastReceiver that listens for the
// ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular
// Loader whenever the receiver detects that a new application has been installed.
// Please don’t hesitate to leave a comment if you still find this confusing! :)
////private SampleObserver mObserver;
}
Snippet 3: How to call the loader for the first time (ONLY)
// Initialize a Loader with an id. If the Loader with this id is not
// initialized before
getLoaderManager().initLoader(LOADER_ID, null, this);
Snippet 4: For refreshing data (recalling the query)
// Check if the loader exists and then restart it.
if (getLoaderManager().getLoader(LOADER_ID) != null)
getLoaderManager().restartLoader(LOADER_ID, null, this);
Reference:
Snippet 1 : usage of loader extracted from here
Snippet 2 : here for more info and logic read throughout the hole article
Snippet 3 & 4: are just loader usage.
Full code of these is also uploaded by the creator on github
I recommend OrmLite library, a lightweight Object Relational Mapping that can work for Android. This library will make your life easier . You don't need to create or update database by hand, you don't need to focus on managing database connection, all queries select, insert, update will be easier with a DAO approach (usually you don't need to write your own sql query) and a lot of features. They have some examples that you can start with.
And if you want to use the Loader, there is a ORMLite Extras , additional functionality for ORMLite available on github (You can use the support package which is compatible with support android library). Here is an example usage on my previous project:
public class EventsFragment extends Fragment implements LoaderCallbacks<Cursor>{
private static final int LOADER_ID = EventsFragment.class.getName().hashCode();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layoutRoot = inflater.inflate(
R.layout.fragment_events, null);
lvEvents = (ListView) layoutRoot.findViewById(R.id.lvEvents);
adapter = new EventAdapter(getActivity(), null, null);
lvEvents.setAdapter(adapter);
return layoutRoot;
}
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
try {
PreparedQuery<Event> query = getDatabaseHelper().getEventDao().getQuery();
return getDatabaseHelper().getEventDao().getSQLCursorLoader(query );
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
adapter.swapCursor(cursor);
try {
adapter.setQuery(getDatabaseHelper().getEventDao().getQuery());
} catch (SQLException e) {
e.printStackTrace();
}
}
#Override
public void onLoaderReset(Loader<Cursor> arg0) {
adapter.swapCursor(null);
}
private OrmliteDatabaseHelper getDatabaseHelper(){
return ((MainActivity)getActivity()).getDatabaseHelper();
}
}
The adapter
public class EventAdapter extends OrmliteCursorAdapter<Event>{
public EventAdapter(Context context, Cursor c, PreparedQuery<Event> query) {
super(context, c, query);
}
#Override
public void bindView(View itemView, Context context, Event item) {
TextView tvEventTitle = (TextView) itemView.findViewById(R.id.tvEventTitle);
TextView tvEventStartDate = (TextView) itemView.findViewById(R.id.tvEventStartDate);
tvEventTitle.setText(item.getTitle());
tvEventStartDate.setText(item.getFormatStartDate());
}
#Override
public View newView(Context context, Cursor arg1, ViewGroup arg2) {
LayoutInflater inflater = LayoutInflater.from(context);
View retView = inflater.inflate(R.layout.event_item_row, arg2, false);
return retView;
}
}
And a custom Dao which provides PreparedQuery for cursor adapter above:
public interface IEventDao extends Dao<Event, Integer>{
PreparedQuery<Event> getQuery() throws SQLException;
OrmliteCursorLoader<Event> getSQLCursorLoader(Context context, PreparedQuery<Event> query) throws SQLException;
}
public class EventDao extends AndroidBaseDaoImpl<Event, Integer> implements IEventDao{
public EventDao(ConnectionSource connectionSource) throws SQLException {
super(connectionSource, Event.class);
}
public EventDao(ConnectionSource connectionSource,
DatabaseTableConfig<Event> tableConfig) throws SQLException {
super(connectionSource, tableConfig);
}
#Override
public PreparedQuery<Event> getQuery() throws SQLException{
return queryBuilder().prepare();
}
}
Hope this can help!
If your database contains thousands of records consider madlymad's answer
If not keep it stupid and simple, use SQLiteOpenHelper and create a method that returns you your data as array of strings or define your one objects.
Also use custom/regular CursorAdapter or ArrayAdapter.
I use the SQLiteOpenHelper to create my database. I have made Java classes for all the tables and when I get the data from my database I put it in an ArrayList. The ArrayList I then load into the adapter of the Listview.
Related
I have a Parent Fragment, called WaterFountainFragment, that has a nested Fragment progamatically inflated inside a FrameLayout that is dependant of an user's choice in a RadioGroup. If the user chooses one option, it inflates one Child and, when chosen the other option, it inflates another child (in the name of being the most concise as possible, I'll only list one of those child fragments, since the problem happens in both of them).
The user enters the data he wants and save it on the database, using LiveData and Room dependencies to do so. However, if the user wants to go back and check which data was saved in an specific entry, then we start to face trouble.
The problem is, the saved data is shown in the Parent Fragment but, unfortunately, it does NOT load inside the child fragment nested on it.
First, let me show the parent class in which I think everything is working normally:
WaterFountainFragment Class
public class WaterFountainFragment extends Fragment {
(...)
#Override
public void onStart() {
super.onStart();
//modelEntry is a ViewModel, which class ViewModelEntry will be shown later
modelEntry = new ViewModelEntry(requireActivity().getApplication());
//The ID of an entry is sent to this fragment through a bundle (via setArgument());
if (waterFountainBundle.getInt(FOUNTAIN_ID) > 0) {
//getOneWaterFountain is a method inside modelEntry to obtain an entry from the database through
//LiveData and Room
modelEntry.getOneWaterFountain(waterFountainBundle.getInt(FOUNTAIN_ID))
.observe(getViewLifecycleOwner(), this::loadFountainInfo);
}
}
//this is a RadioGroup Listener where it inflates the specific child fragment inside a FrameLayout
public void typeFountainListener(RadioGroup group, int checkedID) {
int index = getCheckedFountainType(group);
switch (index) {
case 0:
getChildFragmentManager().beginTransaction()
.replace(R.id.water_fountain_info, new WaterFountainSpoutFragment()).commit();
break;
case 1:
getChildFragmentManager().beginTransaction()
.replace(R.id.water_fountain_info, new WaterFountainOtherFragment()).commit();
break;
default:
break;
}
}
//method to fill all fields inside this fragment
private void loadFountainInfo(WaterFountainEntry waterFountain) {
fountainLocationValue.setText(waterFountain.getFountainLocation());
typeWaterFountain
.check(typeWaterFountain.getChildAt(waterFountain.getTypeWaterFountain()).getId());
if (waterFountain.getFountainTypeObs() != null)
fountainTypeObsValue.setText(waterFountain.getFountainTypeObs());
//At this point the RadioGroupListener is active already and, when checking a RadioButton, it will
//inflate the child and send a fragmentResult to this child fragment
getChildFragmentManager()
.setFragmentResult(InspectionActivity.LOAD_CHILD_DATA, waterFountainBundle);
}
}
Now we have one of the 2 nested child fragments that are inflated:
WaterFountainSpoutFragment Class
public class WaterFountainSpoutFragment extends Fragment {
#Override
public void onStart() {
super.onStart();
//the ViewModel is instantiated here with the same name and method as the
//one instantiated in the ParentFragment
modelEntry = new ViewModelEntry(requireActivity().getApplication());
//FragmentResultListener is active and it does recieve the signal from the parent Fragment.
getParentFragmentManager()
.setFragmentResultListener(InspectionActivity.LOAD_CHILD_DATA, this, (key, bundle) ->
//However, inside this resultListener, this observer seems to not be triggered
modelEntry.getOneWaterFountain(bundle.getInt(WaterFountainFragment.FOUNTAIN_ID))
.observe(getViewLifecycleOwner(), this::loadSpoutFountainData)
);
}
}
Here we have the ViewModel, the repository and the Dao classes/interfaces that were used on those 2 classes above
ViewModelEntry class
public class ViewModelEntry extends AndroidViewModel {
public ViewModelEntry(#NonNull Application application) {
super(application);
repository = new ReportRepository(application);
allEntries = repository.getAllSchoolEntries();
}
public LiveData<WaterFountainEntry> getOneWaterFountain(int waterFountainID) {
return repository.getOneWaterFountain(waterFountainID);
}
}
ReportRepository Class
public class ReportRepository {
private ReportDatabase db;
private final WaterFountainDao waterFountainDao;
public ReportRepository(Application application) {
waterFountainDao = db.waterFountainDao();
}
public LiveData<WaterFountainEntry> getOneWaterFountain(int waterFountainID) {
return waterFountainDao.getOneWaterFountain(waterFountainID);
}
}
WaterFountainDao Interface
#Dao
public interface WaterFountainDao {
#Query("SELECT * FROM WaterFountainEntry WHERE waterFountainID == :waterFountain")
LiveData<WaterFountainEntry> getOneWaterFountain(int waterFountain);
}
What I know/tested so far
Using a Toast, I confirmed that getParentFragmentManager().setFragmentResultListener() is being called. Even more so, the bundle recieve the right data.
If I use the modelEntry.getOneWaterFountain(bundle.getInt(WaterFountainFragment.FOUNTAIN_ID)).observe(getViewLifecycleOwner(), this::loadSpoutFountainData) outside the resultListener, it does load the correct data into the child Fragment fields.
The data entered by the user IS being stored in the database. I confirmed that using the Database Inspector, so it is not a case where "the data is not being stored properly, hence why is not loading".
I use the same method in other Parent/Child Fragments, using the same format of resultListener and it DOES load the data.
Using another method for creating this ViewModel, like modelEntry = new ViewModelProvider.AndroidViewModelFactory(requireActivity().getApplication()).create(ViewModelEntry.class); in both parent and child fragments results in the same problem
What I SUPPOSE it might be the case
I have wondered that it might be a situation where I am choosing the wrong LyfecycleOwner but I don't know if that is the case, mainly because of what I put on item 4.
Any help would be greatly appreciated.
This gets called when a button is clicked
#Override
public void onFavoriteIconClicked() {
viewModel.isFavoriteExist(test.getId()).observe(getViewLifecycleOwner(), new Observer<Boolean>() {
#Override
public void onChanged(Boolean aBoolean) {
viewModel.isFavoriteExist(test.getId()).removeObserver(this);
if (aBoolean) {
binding.addToFavorite.setImageResource(R.drawable.non_fav);
viewModel.delete(test);
} else if (getActivity() != null) {
Test test2 = new Test(test.getId(), test.getName());
viewModel.insert(test2);
binding.addToFavorite.setImageResource(R.drawable.fav);
}
}
});
}
If the test object exists in the Favorite database, I have to delete it. After deleting, it calls this again (since it observed a chane) and inserts it again.
It keeps looping infinitely. Is there a better way to implement this or stop this?
It seems like some business logic has entered your view (Activity) class.
Since LiveData & Room are meant to be used when receiving updates about Database changes is needed, and your use of the DB is not requiring constant updates, I would suggest going with a more direct approach.
First, Remove the use of LiveData from your Database. Use simple return values.
Your view (Activity/Fragment) can then tell the view model that a button was clicked.
#Override
public void onFavoriteIconClicked() {
viewModel.onFavoriteClicked()
}
The view will observe the view model in order to receive the correct icon to show.
Something like:
viewModel.favoriteIcon.observe(getViewLifecycleOwner(), new Observer<Integer>() {
#Override
public void onChanged(Integer iconResId) {
binding.addToFavorite.setImageResource(iconResId)
}
}
Now the viewModel can handle the logic (or better add a Repository layer - See Here)
Upon click, Check if entry exist in DB.
If exists: remove it from DB and set favoriteIcon value:
favoriteIcon.setValue(R.drawable.non_fav)
If doesn't exist: Add it to DB and set favoriteIcon value.
favoriteIcon.setValue(R.drawable.fav)
For a good tutorial about using Room & LiveData - as well as doing so using the View/ViewModel/Repository pattern, check this link
I have an android activity with a fragment.
In the fragment, I fetch data using retrofit and set a static flag, so that , when I again go to this fragment, I restrict fetching data again.
I also store the data in a singleton instance.
But even after I destroyed the activity/closed the application, the static flag and the instance is still available and the list is also present in the instance, which malfunctions my app.
But I want the instance to be created newly and fetch data at each run.
This is my singleton instance.
public class Utilities {
private static Utilities utils = null;
private List<Data> friendsList;
public List<Data> getDataList() {
return dataList;
}
public void setDataList(List<Data> dataList) {
this.dataList = dataList;
}
private List<Data> dataList;
public synchronized static Utilities getInstance(){
if(utils == null){
utils = new Utilities();
}
return utils;
}
}
This is my fragment:
public class DataFragment extends Fragment
{
private static boolean hasObtainedData;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if(!hasObtainedData){
getData(v);
}else{
recyclerView.setAdapter(new Adapter(utils.getDataList()));
}
}
private void getData(View v) {
//get Data using Retrofit:
hasObtainedData = true;
utils.setDataList(dataListObtainedUsingRetrofit)
recyclerView.setAdapter(new Adapter(utils.getDataList()));
}
}
This is how, I call my fragment from MainActivity:
#Override
public void onTabSelected(TabLayout.Tab tab) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentBox, new DataFragment()).commit();
}
I tried giving System.gc() at onDestroy() of MainActivity, but still, the singleton instance is alive.
I search many SOF posts based on this, but wasn't able to solve this issue.
Any help will be really useful.
The pointer to the Singleton is static in the Utilities-Class itself, so the Singelton can never be available for GC, unless you set utils = null on leaving MainAcitivity with something like
Utilities.reset();
Your singleton wont survive your app being terminated, and what's probably happening is that you aren't actually killing the app entirely during your tests. Run adb shell am force-stop <your-app-package> from your console and see whether that still results in the issue.
With that said, if you only want your data to run once per application launch, then I would recommend you move it into the onCreate() lifecycle callback of your Application
public class App extends Application {
#Override
public void onCreate() {
super.onCreate();
//Begin process to fetch data and cache it here
}
}
Don't forget to add the application name to your manifest too, <application android:name="your.packagename.App"
You then have no need to modify your data at all - it will only run once per application launch.
If, however, what you actually want is for your data to update every time the Activity is launched, then do the same process as before but inside onCreate() of you Activity. You can also clear it in onDestroy(), if you want it to updated when your Activity is recreated:
#Override
public void onDestry() {
super.onDestroy();
Utilities.getInstance().setDataList(null);
}
Also, if your data is bound to the lifecycle of your Activity, then you don't really need a singleton (which is bound to the lifecycle of the application).
lately i have been researching about memory leaks in java/android and pretty much everywhere it says that instead of anonymous classes i should use static inner classes with weak references.
so, in my android app i started doing that but very quickly got tired of it because it's a lot of boilerplate code... i think have an alternative solution which i would prefer to use, but i'm juts not sure that it is a valid alternative to static inner classes in terms of preventing memory leaks. as i said before, i haven't seen this solution suggested anywhere else (all say to use static inner classes) so thats why im not sure my alternative will work.
ill use a simple example from my app:
i have a class called WebClient which handles asynchronous web requests and it accepts an interface called iCallback which returns the response from the server to the caller, and in my activity once i get this callback i need to dismiss a dialog, and maybe perform some activity related things (like trigger onBackPressed() and setResult()).
so here is my static inner class i have created:
private static class CallBack implements WebClient.ICallback
{
private WeakReference<ProgressDialog> mProgDiag;
private WeakReference<BaseActivity> mActivity;
public CallBack(BaseActivity activity, ProgressDialog progDiag)
{
this.mProgDiag = new WeakReference<>(progDiag);
this.mActivity = new WeakReference<>(activity);
}
#Override
public void onCallback(String data)
{
String responseAsString = Utils.extractStringFromResponse(...);
final BaseActivity parentActivity = mActivity.get();
ProgressDialog dialog = mProgDiag.get();
if(dialog != null)
{
dialog.dismiss();
}
if (responseAsString == null)
{
if(parentActivity != null)
{
Utils.makeServerErrorDialog(parentActivity,
new iDialogButtonClickedListener()
{
#Override
public void onDialogButtonClicked()
{
parentActivity.onBackPressed();
}
});
}
return;
}
//everything is ok
if (responseAsString.equals("1"))
{
if(parentActivity != null)
{
Intent result = new Intent();
result.putExtra(...);
parentActivity.setResult(Activity.RESULT_OK, result);
}
}
else
{
Utils.reportErrorToServer(...);
if(parentActivity != null)
{
parentActivity.setResult(Activity.RESULT_CANCELED);
}
}
if(parentActivity != null)
{
parentActivity.onBackPressed();
}
}
}
so for every variable i need in this static inner class i have to create a new weak reference, then retrieve the object itself, and then every time i want to access it i need to check whether it's null... that seems like a lot of code to me.
and here is my suggested alternative:
public abstract class BaseActivity extends AppCompatActivity
implements WebClient.ICallback
{
private static final String TAG = "BaseActivity";
WebClient.ICallback mCallBack;
ProgressDialog mProgDiag;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(...);
mCallBack = this;
//some code to invoke a server request on button click
//and passing mCallBack to the request
}
#Override
public void onCallback(String data)
{
String responseAsString = Utils.extractStringFromResponse(...);
mProgDiag.dismiss();
if (responseAsString == null)
{
Utils.makeServerErrorDialog(this,
new iDialogButtonClickedListener()
{
#Override
public void onDialogButtonClicked()
{
onBackPressed();
}
});
return;
}
//everything is ok
if (responseAsString.equals("1"))
{
Intent result = new Intent();
result.putExtra(...);
setResult(Activity.RESULT_OK, result);
}
else
{
Utils.reportErrorToServer(...);
setResult(Activity.RESULT_CANCELED);
}
onBackPressed();
}
#Override
protected void onPause()
{
mCallBack = null;
super.onPause();
}
#Override
protected void onResume()
{
super.onResume();
mCallBack = this;
}
}
to me this seems much cleaner: no creating and retrieving instances of weak references for every variable i need access to, i can directly invoke activity methods (e.g. onBackPressed()), and no checking for null everywhere.
the only place i would now have to check for null is inside WebClient class before invoking the callBack method.
so my question is, does this approach achieve the same result in terms of preventing memory leaks? is it a "worthy" alternative to static inner classes?
Unfortunately, your approach does not work. By implementing the WebClient.ICallback in your activity, rather than an inner class, you don't get rid of the leak. The leak happens not because the references to activity and dialog are implicit in an anonymous class, or in lambda, or in a non-static inner class instance; the happens when the WebClient keeps this reference while the activity is gone (it is not destroyed, because there is a strong reference to it).
The special mCallBack that you set to null when the activity is paused, gains nothing. Just as well, you can simply pass your activity instance to the WebClient. Now there is a strong reference to your activity, which is managed by someone (async handlers of the WebClient), who is not under your control. If you are unlucky, the async handler will get stuck somewhere and will never release this reference.
Please read this detailed explanation.
Note that WebView itself can cause a memory leak, if special measures are not undertaken!
I have a fragment, and want to start a loader when a button is clicked:
public class MyFragment extends Fragment {
public void onActivityCreated() {
super.onActivityCreated();
Button btn = ...;
btn.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
getLoaderManager().initLoader(500, null, mMyCallback);
}
});
}
private LoaderManager.LoaderCallbacks<String> mMyCallback = new LoaderManager.LoaderCallbacks<String>() {
#Override
public Loader<String> onCreateLoader(int arg0, Bundle arg1) {
Log.e(TAG, "LoaderCallback.onCreateLoader().");
return new MyLoader(getActivity());
}
}
}
public class MyLoader extends AsyncTaskLoader<String> {
public MyLoader(Context context) {
super(context);
}
#Override
public String loadInBackground() {
Log.e(TAG, "Hi, running.");
return "terrific.";
}
}
After clicking the button, I can see my callback's onCreateLoader method called, but the created loader never actually starts. Do we need to call forceLoad() on the loader itself to get it to actually start? None of the sample posts do this,
Thanks
You need to implement onStartLoading() and call forceLoad() somewhere in the method.
See this post for more information: Implementing Loaders (part 3)
In my experience it never worked unless I used forceLoad().
You may find the answer to this previous question helpful:
Loaders in Android Honeycomb
Three important points regarding Loaders are:
Always Use forceLoad() method while initialising Loaders. For Example:
getLoaderManager().initLoader(500, null, mMyCallback).forceLoad();
Always implement onStartLoading(). This function will automatically be called by LoaderManager when the associated fragment/activity is being started.
Make sure the ID of the loader is unique otherwise new Loader will not be called.
If there is still a problem you can check the state of loader by calling the isStarted() method.
You need to keep a reference to the instance of the loader you create in the method onCreateLoader. Then, to refresh it, call yourLoader.onContentChanged();
If you have more than 1 loader in the same activity, make sure their id differs. I lost few hours to figure it out :)