Search code examples
androidsqliterx-javarx-androidsqlbrite

Cursor in observable not closed on orientation change


While running on strict mode, when the orientation of the phone change, my app will crash with the error

A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks. java.lang.Throwable: Explicit termination method 'close' not called

Weird thing is, if I run the app in debug mode and put breakpoints at all cursor.close() just to make sure it's actually being hit, all the breakpoints are hit and the app will never crash.

Here's my fragment which calls another class that contains all the SqlBrite queries and cursors (code has been shorten for brevity).

public class MenuSummary extends Fragment{

private DbHelper dbHelper;
private Observable<?> income1, income2;
private Observable imin;
private Subscription s;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    dbHelper = new DbHelper(getContext());
}

@Override
public void onPause(){
    super.onPause();
    s.unsubscribe();
}

@Override
public void onResume(){
    super.onResume();
    displaySummary(selectedYear,selectedMonth);
}

@Override
public void onStop() {
    super.onStop();
}

@Override
public void onActivityCreated(Bundle savedInstanceState){
    super.onActivityCreated(savedInstanceState);
}

private void displaySummary(final int selectedYear, final int selectedMonth){

    income1 = dbHelper.getIncome(selectedYear,selectedMonth,1);
    income2 = dbHelper.getIncome(selectedYear,selectedMonth,2);

    List<Observable<?>> myObservables = Arrays.asList(income1,income2);
    imin = Observable.combineLatest(myObservables, new FuncN<List<BigDecimal>>() {
        @Override
        public List<BigDecimal> call(Object... args) {
            List<BigDecimal> listIncome = new ArrayList<~>();
            listIncome.add((BigDecimal) args[0]);
            listIncome.add((BigDecimal) args[1]);
            return listIncome ;
        }
    }
).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
s = imin.subscribe(new Action1<List<BigDecimal>>() {
@Override
public void call(List<BigDecimal> expensesAndBalance) {
    //do plenty of view.settext here
}});
}

And here's how the DbHelper class which contains the function getIncome looks like:

Can anyone pinpoint where did I went wrong?

   public class DbHelper {

        private MySQLiteHelper mySQLiteHelper;
        SqlBrite sqlBrite = new SqlBrite.Builder().build();
        BriteDatabase briteDb;
        Subscription subscription;

        private static DbHelper instance;
        public DbHelper(Context context) {
            mySQLiteHelper = new MySQLiteHelper(context);
            sqlBrite = new SqlBrite.Builder().build();
            briteDb = sqlBrite.wrapDatabaseHelper(mySQLiteHelper,Schedulers.io());
        }

        public Observable<BigDecimal> getIncome(final int endYear, final int endMonth, final int moneyJar){
        final String[] args = new String[]{moneyJar + "", endYear + "", endMonth + "", endYear + "", };
        Observable<BigDecimal> myObservable;
        myObservable = briteDb.createQuery(MySQLiteHelper.TABLE_INCOME, "SELECT total FROM " + MySQLiteHelper.TABLE_INCOME +
                " WHERE moneyJar = ? AND ((year = ? AND month <= ?) OR (year < ?)) and isDeleted = 0", args)
                .map(new Func1<SqlBrite.Query, BigDecimal>() {
                    @Override
                    public BigDecimal call(SqlBrite.Query query) {
                        Cursor cursor = query.run();
                        BigDecimal income, incomeTotal = new BigDecimal(0);
                        if (cursor != null) {
                            try {
                                if (cursor.getCount() > 0 && cursor.moveToFirst()) {
                                    do {
                                        income = new BigDecimal(cursor.getString(cursor.getColumnIndex("total")));
                                        incomeTotal = incomeTotal.add(income);
                                    } while (cursor.moveToNext());
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            } finally {
                                cursor.close();
                            }
                        }
                        return incomeTotal;
                    }
                });
        return myObservable;
    }
}

Solution

  • You are not closing your BriteDatabase instance.

    You should perform this when your fragment is being closed:

    briteDb.close();
    

    From the docs:

    Close the underlying SQLiteOpenHelper and remove cached readable and writeable databases. This does not prevent existing observables from retaining existing references as well as attempting to create new ones for new subscriptions.