No adapter attached; skipping layout when clicking back button and starting the app again

Multi tool use
Multi tool use


No adapter attached; skipping layout when clicking back button and starting the app again


Android Studio 3.2 Canary 18
kotlin_version = 1.2.50



I have a simple app that uses a recyclerview and adapter. When the app starts is load all the data.
However, when I click the back button and start the app again. It won't display the data (blank).
If I clear the app from memory and start the app. The data will load as normal.



I am loading the data from sqlite and the data is loaded each time. as it populates the insectDataModelList.


insectDataModelList



After going into the RecyclerView.java source code the reason is the mAdapter is null. However, I have
checked that the adapter is correct when I set it to the recyclerview.


RecyclerView.java


mAdapter


void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
...
}



My MainActivity.java is Java


public class MainActivity extends AppCompatActivity {
private RecyclerView rvInsects;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
rvInsects = (RecyclerView)findViewById(R.id.recycler_view);

DatabaseManager databaseManager = DatabaseManager.getInstance(this);
databaseManager.queryAllInsects("friendlyName");
}

private void setupAdapter(List<InsectDataModel> insectDataModelList) {
final LayoutManager layoutManager = new LinearLayoutManager(
this, LinearLayoutManager.VERTICAL, false);
rvInsects.setLayoutManager(layoutManager);
rvInsects.setHasFixedSize(true);
final InsectAdapter insectAdapter = new InsectAdapter(insectDataModelList);
rvInsects.setAdapter(insectAdapter);
insectAdapter.notifyDataSetChanged();
}

/* Callback from database */
public void loadAllInsects(final Cursor cursor) {
InsectInteractorMapper insectInteractorMapper = new InsectInteractorMapperImp();
final List<InsectDataModel> insectDataModelList = insectInteractorMapper.map(cursor);
/* data loaded with 24 items */
setupAdapter(insectDataModelList);
}
}



InsectAdapter.kt is Kotlin.


class InsectAdapter(private val insectList: MutableList<InsectDataModel>)
: RecyclerView.Adapter<InsectAdapter.CustomInsectHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomInsectHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.insect_row_item, parent, false)
return CustomInsectHolder(view)
}

override fun onBindViewHolder(holder: CustomInsectHolder, position: Int) {
holder.tvFriendlyName.text = insectList[position].friendlyName
holder.tvScientificName.text = insectList[position].scientificName
}

override fun getItemCount(): Int {
return insectList.size
}

class CustomInsectHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val ivDangerLevel: DangerLevelView = itemView.findViewById(R.id.ivDangerLevel)
val tvFriendlyName: TextView = itemView.findViewById(R.id.tvFriendlyName)
val tvScientificName: TextView = itemView.findViewById(R.id.tvScientificName)
}
}



The database I use rxjava2 to do the query


public class DatabaseManager {
private static DatabaseManager sInstance;
private MainActivity mainActivity;
private BugsDbHelper mBugsDbHelper;

public static synchronized DatabaseManager getInstance(MainActivity context) {
if (sInstance == null) {
sInstance = new DatabaseManager(context);
}

return sInstance;
}

private DatabaseManager(MainActivity context) {
mBugsDbHelper = new BugsDbHelper(context);

mainActivity = context;
}

@SuppressLint("CheckResult")
public void queryAllInsects(String sortOrder) {
final InsectStorageInteractorImp insectStorageInteractorImp
= new InsectStorageInteractorImp(new InsectStorageImp(mBugsDbHelper.getReadableDatabase()));

insectStorageInteractorImp.getAllSortedInsects(sortOrder)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Cursor>() {
Disposable disposable;

@Override
public void onSubscribe(Disposable d) {
disposable = d;
}

@Override
public void onSuccess(Cursor cursor) {
mainActivity.loadAllInsects(cursor);
disposable.dispose();
}

@Override
public void onError(Throwable e) {
disposable.dispose();
}
});
}
}



Everything works as expected when the apps installs for the first time. And if you clear it out of memory.
However, its only when you click the back button, and then try and start the app it will not load any data
because of the mAdapter being null in the RecyclerView class.



When I click the back button and then start the app again. All I get is a blank screen i.e.



enter image description here



Updated DatabaseManager class that removes the singleton and used a weakreference to ensure that the MainActivity instance is garbage collected.


public class DatabaseManager {
private WeakReference<MainActivity> mainActivity;
private BugsDbHelper mBugsDbHelper;

public DatabaseManager(MainActivity context) {
mBugsDbHelper = new BugsDbHelper(context);
mainActivity = new WeakReference<>(context);
}

@SuppressLint("CheckResult")
public void queryAllInsects(String sortOrder) {
final InsectStorageInteractorImp insectStorageInteractorImp
= new InsectStorageInteractorImp(new InsectStorageImp(mBugsDbHelper.getReadableDatabase()));

insectStorageInteractorImp.getAllSortedInsects(sortOrder)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Cursor>() {
Disposable disposable;

@Override
public void onSubscribe(Disposable d) {
disposable = d;
}

@Override
public void onSuccess(Cursor cursor) {
mainActivity.loadAllInsects(cursor);
disposable.dispose();
}

@Override
public void onError(Throwable e) {
disposable.dispose();
}
});
}
}



Many thanks for any suggestions,





try calling insectAdapter.notifyDataSetChanged(); in your onResume method.
– A.N.T
Jun 25 at 14:44





Good luck with attestation, sir! 🤓
– azizbekian
Jun 26 at 8:08




5 Answers
5



When you click the back button and relaunch the app, a new instance of MainActivity is started.


MainActivity



At the same time, your DatabaseManager is a singleton. Its reference is stored as a static variable. It survives the activity recreation. It will live until the process is killed.


DatabaseManager



So, when you run queryAllInsects for the second time, the callback is sent to the old instance of MainActivity, which is not visible anymore.


queryAllInsects


MainActivity



You should not keep a reference to MainActivity in DatabaseManager. It's a memory leak, because it cannot be garbage collected.


MainActivity


DatabaseManager





Thanks that solved my problem and makes perfect sense. I have updated my question with the DatabaseManager class that removes the singleton and uses a weakreference for the MainActivity to ensure that its garbaged collected. Can you see any other problem with this new design? Thanks
– ant2009
Jun 25 at 15:44






When you call DatabaseManager.getInstance(this) for the second time, the reference to the activity is not updated, because DatabaseManager already exists. Instead, you can pass a callback to queryAllInsects.
– dev.bmax
Jun 25 at 16:14



DatabaseManager.getInstance(this)


DatabaseManager


queryAllInsects



The issue is most likely that you are loading the data in your onCreate() and not in onResume(). When you press back to "close the app" you are not necessarily clearing the UI stack from memory. That's why when you go back into the app, it doesn't invoke onCreate() again, and doesn't load your data again.



Keep everything the same, just move your data loading from onCreate() to onResume(). That way, whenever the screen is shown to the user, the data will load.





I moved the data loading from onCreate to onResume. However, I had the same problem and the data is not loaded.
– ant2009
Jun 25 at 15:35



Few observations:





Thanks for the suggestions, I will take this into account
– ant2009
Jul 3 at 15:08



Put this 2 lines in onResume() and remove from onCreate() and try it.


DatabaseManager databaseManager = DatabaseManager.getInstance(this);
databaseManager.queryAllInsects("friendlyName");



I suggest the following changes:



MainActivity, the less code you write in the activity the better, move all the data retrieval part to the DatabaseManager. Also setup the RecyclerView once and only update the dataset when appropriate:


DatabaseManager


RecyclerView


public class MainActivity extends AppCompatActivity {

private List<InsectDataModel> insectDataModelList = new ArrayList<>();

private Disposable disposable;
private RecyclerView rvInsects;
private InsectAdapter insectAdapter;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
setupAdapter();

//Request Data, take advantage of RxJava to load data asynchronously
DatabaseManager.getInstance(this)
.queryAllInsects("friendlyName")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<List<InsectDataModel>>() {
@Override
public void onSubscribe(Disposable d) {
disposable = d;
}

@Override
public void onSuccess(List<InsectDataModel> response) {
insectDataModelList.clear();
insectDataModelList.addAll(response);
insectAdapter.notifyDatasetChanged();
}

@Override
public void onError(Throwable e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
});;
}

private void setupAdapter() {
//Setup RecyclerView Only need to be called once
rvInsects = (RecyclerView)findViewById(R.id.recycler_view);
LayoutManager layoutManager = new LinearLayoutManager(this); // LinearLayoutManager is Vertical by default
rvInsects.setLayoutManager(layoutManager); // You don't event have to define it as RecyclerView use LinearLayoutManager.Vertical by default
rvInsects.setHasFixedSize(true);
insectAdapter = new InsectAdapter(insectDataModelList);
rvInsects.setAdapter(insectAdapter);
}


@Override
protected void onDestroy() {
//Dispose observer if activity is destroyed to prevent memory leak
if(disposable != null && !disposable.isDisposed())
disposable.dispose();

super.onDestroy();
}
}



And in DatabaseManager, instead of observing the data source(Cursor) and notify the requester(Activity) via callback, we get the data stream and pass it the caller to observe:


public class DatabaseManager {
private static DatabaseManager sInstance;
private BugsDbHelper mBugsDbHelper;

public static synchronized DatabaseManager getInstance() {
if (sInstance == null) {
sInstance = new DatabaseManager();
}

return sInstance;
}

private DatabaseManager() {
// Move the actualy database initiation to application class or singleton
mBugsDbHelper = BugsDbHelper.getInstance(); // or ApplicationController.getDbHelper();
}

@SuppressLint("CheckResult")
public SingleObserver<List<InsectDataModel>> queryAllInsects(String sortOrder) {
final InsectStorageInteractorImp insectStorageInteractorImp
= new InsectStorageInteractorImp(new InsectStorageImp(mBugsDbHelper.getReadableDatabase()));

insectStorageInteractorImp.getAllSortedInsects(sortOrder)
.map(new Function<Cursor, List<Object>>() {
@Override
public List<Object> apply(Cursor cursor) throws Exception {
InsectInteractorMapper insectInteractorMapper = new InsectInteractorMapperImp();
return insectInteractorMapper.map(cursor);
}
});
}
}



Now the solution here is to rely on the RxJava to change the callback pattern to the observer pattern. So instead of passing the activity (callback) and waiting to be called, we get the data steram (observable) and observe it for the response. This eliminate the leak problem all together and enhance the readability and maintainability.


RxJava



Also don't forget to move the Database initialization to the Application class or a Singleton instance to prevent multiple instantiation. The easier solution would be like:


Database


public class ApplicationController extends Application {

private BugsDbHelper mBugsDbHelper;

@Override
public void onCreate() {
super.onCreate();
mBugsDbHelper = new BugsDbHelper(this);
}

public BugsDbHelper getDbHelper(){
return mBugsDbHelper ;
}
}





Thanks, I will take a look at this. however, I am planning to move the rxJava to the presenter that I will create.
– ant2009
Jul 3 at 15:09






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

2,4t4jh9XIPlNSvt iZAEqJXj1RrIYUcHTlQL3fATsGte0f8dYLf qqAvGLI9gVJ96 b
ZNCTEX turc,DdTtCLSAr6dN95UiviW m8Avl woJq

Popular posts from this blog

Rothschild family

Cinema of Italy