1

My applications hang for a bit when I populate data from realm database to my listview.
So I planned to do it using Asynchronously so meanwhile data is collected I display a Loading dialogue here is the Code.
Already referred to this question by not able to implement in my case.

 private class YourAsyncTask extends AsyncTask<String, String, RealmResults> {

    ProgressDialog progressDialog;
    @Override
    protected void onPreExecute() {
        // start loading animation maybe?
        progressDialog = ProgressDialog.show(DictionarySscWords.this,
                "ProgressDialog",
                "Loading all words!");
    }

    @Override
    protected RealmResults doInBackground(String... params) {
        RealmConfiguration realmConfig = new RealmConfiguration.Builder(context).build();
        Realm.setDefaultConfiguration(realmConfig);
        realm = realm.getDefaultInstance();
        RealmQuery<Word> query = realm.where(Word.class);

        for (int i = 0; i < words_for_ssc[Integer.parseInt(params[0])].length; i++) {
            if (i == words_for_ssc[Integer.parseInt(params[0])].length - 1) {

                query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i]);
            } else {
                query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i])
                        .or();

            }

        }
        sscresult = query.findAll(); //error 1
        return sscresult;

    }

    @Override
    protected void onPostExecute(RealmResults r) {
        progressDialog.dismiss();
        list.setAdapter(new MyAdapter(sscresult)); //error 2
        realm.close();
    }
}

ok so there are two problems if anyone can be solved my application would be error-free

  1. if I try to run list.setAdapter(new MyAdapter(sscresult)); in background process the error is:-

this can run only in UI thread

  1. if try to run in postExecute error is :-

Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.

I am not able to solve this issue please help

phpdroid
  • 1,642
  • 1
  • 18
  • 36

2 Answers2

1

You can have your query evaluated on a background thread using asynchronous query API in Realm.

private OrderedRealmCollectionChangeListener<RealmResults<User> callback = new OrderedRealmCollectionChangeListener<>() {
    @Override
    public void onChange(RealmResults<User> results, OrderedCollectionChangeSet changeSet) {
        if (changeSet == null) {
            // The first time async returns with an null changeSet.
        } else {
            // Called on every future update.
        }
    }
};

private RealmResults<User> result;

public void onStart() {
    result = realm.where(User.class).findAllAsync();
    result.addChangeListener(callback);
}

But if you give the RealmResults to a RealmRecyclerViewAdapter, then this is all automatic.

P.S. not closing Realm instance in doInBackground() is like, S-class horrible mistake. Please close your Realm instance on non-looping background threads.


Specifically the following:

    // private class YourAsyncTask extends AsyncTask<String, String, RealmResults> {
    //
    //    ProgressDialog progressDialog;
    //    @Override
    //    protected void onPreExecute() {
    //        // start loading animation maybe?
    //        progressDialog = ProgressDialog.show(DictionarySscWords.this,
    //                "ProgressDialog",
    //                "Loading all words!");
    //    }
    //
    //    @Override
    //    protected RealmResults doInBackground(String... params) {
    //        RealmConfiguration realmConfig = new RealmConfiguration.Builder(context).build();
    //        Realm.setDefaultConfiguration(realmConfig);
    //        realm = realm.getDefaultInstance();
    //        RealmQuery<Word> query = realm.where(Word.class);
    //
    //        for (int i = 0; i < words_for_ssc[Integer.parseInt(params[0])].length; i++) {
    //            if (i == words_for_ssc[Integer.parseInt(params[0])].length - 1) {
    //
    //                query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i]);
    //            } else {
    //                query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i])
    //                        .or();
    //
    //            }
    //
    //        }
    //        sscresult = query.findAll(); //error 1
    //        return sscresult;
    //
    //    }
    //
    //    @Override
    //    protected void onPostExecute(RealmResults r) {
    //        progressDialog.dismiss();
    //        list.setAdapter(new MyAdapter(sscresult)); //error 2
    //        realm.close();
    //    }
    //}

and

public class MyActivity extends AppCompatActivity {
    private RealmResults<Word> words;
    private Realm realm;
    private WordAdapter wordAdapter;

    @BindView(R.id.recycler_view) 
    RecyclerView recyclerView;

    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.my_activity);
        ButterKnife.bind(this);
        realm = Realm.getDefaultInstance();
        words_for_ssc = ...
        RealmQuery<Word> query = realm.where(Word.class);
        String[] array = words_for_ssc[Integer.parseInt(params[0])];
        for (int i = 0; i < array.length; i++) {
            query = query.equalTo("word", array[i]);
            if (i != array.length - 1) {
                query = query.or();
            }
        }
        words = query.findAllSortedAsync("word");
        wordAdapter = new WordAdapter(words);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(wordAdapter);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        realm.close();
        realm = null;
    }
}

public class WordAdapter extends RealmRecyclerViewAdapter<Word, WordViewHolder> {
    public class WordAdapter(OrderedRealmCollection<Word> words) {
        super(words, true);
    }

    @Override
    public WordViewHolder onCreateViewHolder(...) {
        ...
    }

    @Override
    public void onBindViewHolder(WordViewHolder holder, int position) {
        holder.bind(getData().get(position));
    }

    public static class WordViewHolder extends RecyclerView.ViewHolder {
        public WordViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }

        public void bind(Word word) {
            ...
        }
    }
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
-1

I think a cleaner solution to your problem without changing much of the code can be written below. In this case, everything that realm does happen on the background thread inside doInBackground. The realm instance is also closed on the thread it was created.

Now what I did basically is that I extracted a deep copy of the list of Words from RealmResult from realm.copyFromRealm(sscresult) which is completely detached from realm and can be moved around and modified inside any thread. All these objects are now free from realm and can be used in onPostExecute without any worries. The only thing you need to modify is the MyAdapter constructor which doesn't take a RealmResult but instead a List of Words which is exactly what you need and can be iterated the same way as RealmResult was.

The only downside of this approach is that the list of Words will not get synced automatically since they're detached and their value won't change automatically if they get altered inside Realm from somewhere else. But I'm pretty sure though that it won't bother you.

I'm also going to attach an official reference for realm.copyFromRealm() which is here.

 private class YourAsyncTask extends AsyncTask<String, String, List<Word>> {

    ProgressDialog progressDialog;
    @Override
    protected void onPreExecute() {
        // start loading animation maybe?
        progressDialog = ProgressDialog.show(DictionarySscWords.this,
                "ProgressDialog",
                "Loading all words!");
    }

    @Override
    protected List<Word> doInBackground(String... params) {
        RealmConfiguration realmConfig = new RealmConfiguration.Builder(context).build();
        Realm.setDefaultConfiguration(realmConfig);
        try(realm = realm.getDefaultInstance()) {
            RealmQuery<Word> query = realm.where(Word.class);

            for (int i = 0; i < words_for_ssc[Integer.parseInt(params[0])].length; i++) {
                if (i == words_for_ssc[Integer.parseInt(params[0])].length - 1) {

                    query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i]);
                } else {
                    query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i])
                            .or();

                }

            }
            // Here's the sort. Use findAllSorted instead.
            // You can change Sort.ASCENDING to Sort.DESCENDING to reverse 
            // the order.
            sscresult = query.findAllSorted("word", Sort.ASCENDING); 

            // This is where the magic happens. realm.copyFromRealm() takes 
            // a RealmResult and essentially returns a deep copy of the 
            // list that it contains. The elements of this list is however 
            // completely detached from realm and is not monitored by realm 
            // for changes. Thus this list of values is free to move around 
            // inside any thread.

            ArrayList<Word> safeWords = realm.copyFromRealm(sscresult);
            realm.close();
            return safeWords;
        }
    }

    @Override
    protected void onPostExecute(List<Word> words) {
        progressDialog.dismiss();

        // Please note here MyAdaptor constructor will now take the 
        // list of words directly and not RealmResults so you slightly 
        // modify the MyAdapter constructor.

        list.setAdapter(new MyAdapter(words)); 

    }
}

Hope it helps!

Mustansir Zia
  • 984
  • 10
  • 18