Reputation: 6308
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
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
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