emen
emen

Reputation: 6308

Realm accessed from incorrect thread, with Dagger

I am currently learning Dagger, RxJava, Realm and MVP in a simple project.

Basically what this app can do is it can view, add, delete and update data from database, which I'm using Realm.

I have decided to follow MVP architecture and applied repository pattern as well for data manipulation at the back end layer.

For an extra learning, I added Dagger for the dependency injection in the architecture.

Before this, I have developed an app without applying MVP nor repository pattern, not even Dagger and RxJava in mind. All seems to work well without any errors from Realm threading system. Maybe because I tied everything in a single class.

So, now that I'm moving away from that approach, I'm now having trouble implementing it in the new approach, which I think is more loosely coupled and should be better if implemented correctly.


Enough of introduction, let's get back to the topic.

The issue I'm facing right now is Realm always giving me this error:

Exception has been thrown: Realm accessed from incorrect thread.

I was suspecting that my Dagger graph isn't properly managed (especially on providing Realm instance), thus whenever I make query for data, it gives me the error.

So, my Dagger component looks like this:

@Singleton
@Component(modules = {ContextModule.class, RepositoryModule.class, PresenterModule.class})
public interface AppComponent {

    /* Inejct application */
    void inject(FourdoApp fourdoApp);

    /* Realm Helper */
    void inject(DatabaseRealm databaseRealm);

    /* Activity */
    void inject(MainActivity mainActivity);
    void inject(TaskDetailActivity taskDetailActivity);

    /* Presenter*/
    void inject(MainPresenter mainPresenter);
    void inject(TaskDetailPresenter taskDetailPresenter);

    /* Model repository*/
    void inject(TaskRepositoryImpl taskRepository);

}

Inside RepositoryModule.class;

@Module
public class RepositoryModule {

    @Provides
    @Singleton
    Repository<Task> provideTaskRepository() {
        return new TaskRepositoryImpl();
    }

    // Here I provide DatabaseRealm class instance

    @Provides
    @Singleton
    public DatabaseRealm provideDatabaseRealm() {
        return new DatabaseRealm();
    }

}

Not sure whether I did this correctly or not. You can view the source for DI here.

For the data request to happen, inside MainActivity, I injected MainPresenter and call onRequestData interface to request it from the Presenter. From there, Presenter will make the call to Repository for the said data.

public class MainActivity extends BaseActivity implements MainContract.View {

    @Inject
    MainPresenter mainPresenter;

    // ...

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


        // Injecting MainActivity class
        Injector.getAppComponent().inject(this);


        mainPresenter.attachView(this);


        // Requesting for data from Presenter
        mainPresenter.onRequestData();

    }

    // ...

    @Override
    public void onRequestDataSuccess(List<String> taskList) {
        doAdapter.addAll(taskList);
        doAdapter.notifyDataSetChanged();
    }
}

Inside MainPresenter, I injected Repository interface to make request from TaskRepositoryImpl for the real data from database.

public class MainPresenter extends BasePresenter<MainContract.View> implements MainContract.Presenter {

    @Inject
    Repository<Task> taskRepository;

    public MainPresenter() {
        Injector.getAppComponent().inject(this);
    }

    @Override
    public void onRequestData() {
        requestData();
    }

    private void requestData() {

        taskRepository.findAll()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .map(this::mapToStringList)
                .subscribe(new Observer<List<String>>() {

                    @Override
                    public void onNext(List<String> strings) { // Error in this line
                        if (strings.size() > 0) {
                            mView.onRequestDataSuccess(strings);
                        } else {
                            mView.showEmpty();
                        }
                    }
                });
    }
}

Inside TaskRepositoryImpl, here is how I did the findAll and it should return data from DatabaseRealm:

@Override
public Observable<List<Task>> findAll() {
    return Observable.create(subscriber -> {
        try {
            List<Task> models = databaseRealm.findAll(Task.class);

            subscriber.onNext(models);
            subscriber.onComplete();
        } catch (Exception e) {
            subscriber.onError(e);
        }
    });
}

Code for DatabaseRealm are as follows:

public class DatabaseRealm {
    @Inject
    Context context;

    RealmConfiguration realmConfiguration;

    public DatabaseRealm() {
        Injector.getAppComponent().inject(this);
    }

    public void setup() {
        if (realmConfiguration == null) {
            Realm.init(context);
            realmConfiguration = new RealmConfiguration.Builder()
                    .deleteRealmIfMigrationNeeded()
                    .build();
            Realm.setDefaultConfiguration(realmConfiguration);
        } else {
            throw new IllegalStateException("Realm already configured");
        }
    }

    public Realm getRealmInstance() {
        return Realm.getDefaultInstance();
    }

    public <T extends RealmObject> T add(T model) {
        Realm realm = getRealmInstance();
        realm.beginTransaction();
        realm.copyToRealm(model);
        realm.commitTransaction();
        return model;
    }

    public <T extends RealmObject> T update(T model) {
        Realm realm = getRealmInstance();
        realm.beginTransaction();
        realm.copyToRealmOrUpdate(model);
        realm.commitTransaction();
        return model;
    }

    public <T extends RealmObject> T remove(T model) {
        Realm realm = getRealmInstance();
        realm.beginTransaction();
        realm.copyToRealm(model);
        realm.deleteAll();
        realm.commitTransaction();
        return model;
    }

    public <T extends RealmObject> List<T> findAll(Class<T> clazz) {
        return getRealmInstance().where(clazz).findAll();
    }

    public void close() {
        getRealmInstance().close();
    }
}

Full source code for this flawed code is right here.

I'd like to make it clear that I have limited knowledge on Realm instances being used in Dagger.

I followed this tutorial for the Repository Design Pattern with Realm, but it doesn't include Dagger for its dependency injection.

Can someone guide me on why it is always telling I'm calling Realm from incorrect thread?

Upvotes: 1

Views: 1853

Answers (2)

savepopulation
savepopulation

Reputation: 11921

I think you get this error because of this:

@Override
public Observable<List<Task>> findAll() {
    return Observable.create(subscriber -> {
        try {
            List<Task> models = databaseRealm.findAll(Task.class);

            subscriber.onNext(models);
            subscriber.onComplete();
        } catch (Exception e) {
            subscriber.onError(e);
        }
    });
}

You are subscribing to io Thread but you inject your databaseRealm in Main Thread.

if you get instance in your observable's create you'll not get this error.

@Override
public Observable<List<Task>> findAll() {
    return Observable.create(subscriber -> {
        try {
            Realm realm = getRealmInstance();
            List<Task> models = realm.findAll(Task.class);

            subscriber.onNext(models);
            subscriber.onComplete();
        } catch (Exception e) {
            subscriber.onError(e);
        }
    });
}

Upvotes: 2

Burhanuddin Rashid
Burhanuddin Rashid

Reputation: 5370

You need to setup RealmConfigration only once in the Application class and use the Realm.getDeafultInstance() method to access Realm Database

With Dagger you need to Pass only realm instance in constructor

You can follow this Example and fork it

Its not exactly the same code you posted here.But it might help to understand dagger better with MVP ,RxJava and Realm

Upvotes: 1

Related Questions