Search code examples
androidandroid-roomobserver-patternandroid-architecture-componentsandroid-livedata

How to use LiveData, when the DAO method requires a changing parameter?


In a dictionary app using Room to store words I have a Fragment with a TextField.

When the user enters a word into the TextField, I am extracting the text and send it to a JobIntentService, which then calls the DAO method synchronously on its own thread:

@Dao
public interface MyDao {
    @Query("SELECT 1 FROM dictionary WHERE word = :word")
    int findWord(String word);
}

Finally, the JobIntentService sends the result via LocalBroadcastManager to the hosting Activity and the activity calls a public method on the Fragment.

This path works, but you can see that it is very long and I had to write lot of boilerplate code to implement it.

Recently I've got a hint on Reddit, that LiveData is better for such cases and I have been reading on it and already switched some of my fragments to using it.

So I am trying to switch this Fragment to using LiveData too, by changing the signature of the method to:

@Dao
public interface MyDao {
    @Query("SELECT 1 FROM dictionary WHERE word = :word")
    LiveData<Integer> findWord(String word);
}

and invoking it from my Fragment:

public class MyFragment extends Fragment {
    private static final Pattern PATTERN = Pattern.compile("([A-Z]{2,})");

    private LiveData<Integer> mLiveData;

    private EditText mInputText;
    private CheckedTextView mResultText;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container,
                             Bundle savedInstanceState) {
        ...
        mInputText.addTextChangedListener(new TextWatcher() {
            ...
            @Override
            public void afterTextChanged(Editable s) {
                String word = mInputText.getText().toString().trim().toUpperCase();
                if (PATTERN.matcher(word).matches()) {
                    mLiveData = MyDatabase.getInstance(getContext()).myDao().findWord(word);
                } else {
                    mResultText.setChecked(false);
                }
            }
        });

        mLiveData.observe(this, found -> {
            if (found != null && found == 1) {
                mResultText.setChecked(true);
            } else {
                mResultText.setChecked(false);
            }
        });

        return view;
    }

And the above method of course fails with NPE because mLiveData is null.

But how can I initiate it, when the word parameter to findWord(word) is changing?


Solution

  • I have followed pskink's hint (thank you!) and the following seems to work for me:

    public class MyFragment extends Fragment {
        private static final Pattern PATTERN = Pattern.compile("([A-Z]{2,})");
    
        private MutableLiveData<String> mTrigger = new MutableLiveData<>();
        private LiveData<Integer> mResult = Transformations.switchMap(mTrigger, 
            word -> MyDatabase.getInstance(getContext()).myDao().findWord(word));
    
        private EditText mInputText;
        private CheckedTextView mResultText;
    
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater,
                                 ViewGroup container,
                                 Bundle savedInstanceState) {
            ...
            mInputText.addTextChangedListener(new TextWatcher() {
                ...
                @Override
                public void afterTextChanged(Editable s) {
                    String word = mInputText.getText().toString().trim().toUpperCase();
                    if (PATTERN.matcher(word).matches()) {
                        mTrigger.setValue(word);
                    } else {
                        mResultText.setChecked(false);
                    }
                }
            });
    
            mLiveData.observe(this, found -> {
                if (found != null && found == 1) {
                    mResultText.setChecked(true);
                } else {
                    mResultText.setChecked(false);
                }
            });
    
            return view;
        }