I am new to the concept of multi-threading and also new to RxJava. I think I ran into a race condition in my Android project. To be able to demonstrate it better, I downloaded the google codelabs example RoomWithAView and implemented the same race condition as in my own project.
WordDao
@Dao
public interface WordDao {
@Insert()
void insert(Word word);
@Query("SELECT EXISTS(SELECT word FROM WORD_TABLE WHERE word = :word)")
Single<Integer> wordExists(String word);
}
WordRepository
class WordRepository {
private WordDao mWordDao;
private boolean mWordRedundant;
private final CompositeDisposable mCompositeDisposable = new CompositeDisposable();
WordRepository(Application application) {
WordRoomDatabase db = WordRoomDatabase.getDatabase(application);
mWordDao = db.wordDao();
}
void insert(Word word) {
WordRoomDatabase.databaseWriteExecutor.execute(() -> {
mWordDao.insert(word);
});
}
public boolean isWordRedundant(String word) {
mCompositeDisposable.add(
mWordDao.wordExists(word)
.subscribeOn(Schedulers.computation())
.subscribe(integer -> mWordRedundant = integer == 1));
return mWordRedundant;
}
WordViewModel
public class WordViewModel extends AndroidViewModel {
private WordRepository mRepository;
public WordViewModel(Application application) {
super(application);
mRepository = new WordRepository(application);
}
public boolean isWordRedundant(String word) {
return mRepository.isWordRedundant(word);
}
public void insert(Word word) {
mRepository.insert(word);
}
}
NewWordActivity
public class NewWordActivity extends AppCompatActivity {
public static final String EXTRA_REPLY = "com.example.android.wordlistsql.REPLY";
private EditText mEditWordView;
private WordViewModel mWordViewModel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new_word);
mWordViewModel = new ViewModelProvider(this).get(WordViewModel.class);
mEditWordView = findViewById(R.id.edit_word);
final Button button = findViewById(R.id.button_save);
button.setOnClickListener(view -> {
Intent replyIntent = new Intent();
if (TextUtils.isEmpty(mEditWordView.getText())) {
System.out.println("Word empty");
setResult(RESULT_CANCELED, replyIntent);
}
// possible race condition?
else if (mWordViewModel.isWordRedundant(mEditWordView.getText().toString())) {
System.out.println("Word redundant");
setResult(RESULT_CANCELED, replyIntent);
}
else {
String word = mEditWordView.getText().toString();
System.out.println("Word acceptable");
replyIntent.putExtra(EXTRA_REPLY, word);
setResult(RESULT_OK, replyIntent);
}
finish();
});
}
}
The method isPlayerNameRedundant() always returns false in my case. I tested the SQL query in App Inspection and it returns 1, so the isPlayerNameRedundant() method should return true.
I suspect that since I use Schedulers.computation() in the repository the query is being executed on a background thread and before this thread is finished with its task the main thread returns mWordRedundant.
Is that correct?
If so, what would be the way to resolve this?
Thank you in advance.
You are misusing Rx-java.
Correct way:
WordRepository
WordViewModel
NewWordActivity
So why your before code is not working? Let's see the code in WordRepository before (note the comments):