Наблюдать за одной записью в базе данных с помощью LiveData или MutableLiveData - PullRequest
0 голосов
/ 16 июня 2020

В настоящее время я работаю над приложением, которое должно отслеживать изменения для одной записи в моей базе данных. Приложение структурировано следующим образом: My MainActivity - это aRecyclerView, в котором перечислены все друзья в БД. При нажатии на элемент / друга запускается профиль друга (ProfileActivity), включая кнопку для открытия редактора для редактирования имени, дня рождения, изображения и т. Д. c. Когда пользователь завершает редактирование, новые данные сохраняются в БД, EditActivity закрывается, а приложение возвращается в ProfileActivity. Здесь я хочу, чтобы отображалось измененное.

Я экспериментировал как с LiveData, так и с MutableLiveData. В обоих случаях я использовал общую структуру Activity-ViewModel-Repository-Dao. С LiveData мой Dao.findBy (friendId) возвращает LiveData, а с MutableLiveData Dao.findBy (friendId) возвращает FriendEntity. Проблема с MutableLiveData заключается в том, что изменения в БД нельзя наблюдать напрямую. А с LiveData проблема в том, что нужно каким-то образом инициализировать поле LiveData в моем репозитории, чтобы Activity наблюдала за ним. Но LiveData нельзя инициализировать вручную - поэтому я получаю исключение NullPointerException.

Я попытался вызвать Dao.findBy (-1) в onCreate, чтобы инициализировать мои LiveData, и как только я узнаю реальный friendId, я вызываю его снова, но onChanged больше не запускается.

Есть хорошее решение этой проблемы? Есть ли другой способ инициализировать LiveData? Почему onChanged не запускается во второй раз, когда я вызываю Dao.findBy (friendId) (после вызова Dao.findBy (-1))

Activity:

public class ProfileActivity extends AppCompatActivity {
    MyViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mutable_live_data);

        viewModel = new ViewModelProvider(this).get(MyViewModel.class);

        viewModel.getFoundFriend().observe(this, new Observer<FriendEntity>() {
            @Override
            public void onChanged(FriendEntity friendEntity) {
                if (friendEntity != null){
                    FriendEntity friend = friendEntity.getName();
                    textViewData.setText(name);
                }
             }
        });
    }
}

ViewModel:

public class MyViewModel extends AndroidViewModel {

NormalRepository repository;
LiveData<FriendEntity> foundFriend;

public NormalViewModel(@NonNull Application application) {
    super(application);
    repository = new NormalRepository(application);
    foundFriend = repository.getFoundFriend();
}

public LiveData<FriendEntity> getFoundFriend() {
    return foundFriend;
}

public void find(long id) {
    repository.find(id);
}
}

Репозиторий:

public class NormalRepository {

MyDao dao;
LiveData<FriendEntity> foundFriend;

public NormalRepository(Application application) {
    Database database = Database.getInstance(application);
    this.dao = database.normalDao();

    foundFriend = dao.findBy(-1L);
}

public void find(long id) {
    new FindAsyncTask(dao).execute(id);
}

private class FindAsyncTask extends AsyncTask<Long, Void, LiveData<FriendEntity>>{
    NormalDao dao;

    public FindAsyncTask(NormalDao dao) {
        this.dao = dao;
    }

    @Override
    protected LiveData<FriendEntity> doInBackground(Long... longs) {

        foundFriend = null;

        return dao.findBy(longs[0]);
    }

    @Override
    protected void onPostExecute(LiveData<FriendEntity> friendEntityLiveData) {
        foundFriend = friendEntityLiveData;
    }
}

public LiveData<FriendEntity> getFoundFriend() {
    return foundFriend;
}
}

Дао:

@Dao
public interface MyDao {

@Query("SELECT * FROM friend_table WHERE id=:id")
LiveData<FriendEntity> findBy(long id);

}

Ответы [ 2 ]

1 голос
/ 17 июня 2020

Я немного устарел с Java, потому что я использую Kotlin, но мне кажется, что:

FindAsyncTask бесполезен. Если вы вернете LiveData из запроса, это можно будет запустить в основном потоке. Вам не нужно использовать фоновый поток. Фактически, функция немедленно возвращает LiveData (с нулевым значением в качестве содержимого), а затем Room, используя фоновый поток, выполняет запрос и обновляет LiveData. В этот момент вы получаете свою ценность. Теперь забудьте о MutableLiveData в вашем случае. Это если вам нужно обновить значение, но в этом случае Room позаботится об этом. Таким образом, репо будет примерно таким:

public class NormalRepository {
    MyDao dao;

    // you probably can transform all in a one liner, i bet you need just a context there, not application
    public NormalRepository(Application application) {
        Database database = Database.getInstance(application);
        this.dao = database.normalDao();    
    }

    public LiveData<FriendEntity> find(long id) {
        new dao.findBy(id);
    }
}

ViewModel нужно просто вызвать ваш запрос и сохранить LiveData

public class MyViewModel extends AndroidViewModel {

NormalRepository repository;
LiveData<FriendEntity> foundFriend;

// When you create the view Model you should already know what is the id
public NormalViewModel(@NonNull Application application, long id) {
    super(application);
    repository = new NormalRepository(application);
    foundFriend = repository.repository.find(id);
}

public LiveData<FriendEntity> getFoundFriend() {
    return foundFriend;
}
}

и по активности:

public class ProfileActivity extends AppCompatActivity {
    MyViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mutable_live_data);
        if(savedInstanceState != null)
            long id = savedInstanceState.getLong("MyLong")
        viewModel = new ViewModelProvider(this,ViewModelFactory(application, id)).get(MyViewModel.class);

        viewModel.getFoundFriend().observe(this, new Observer<FriendEntity>() {
            @Override
            public void onChanged(FriendEntity friendEntity) {
                if (friendEntity != null){
                    String name = friendEntity.getName();
                    textViewData.setText(name);
                }
             }
        });
    }
}

так что на данный момент вам нужно только понять, как построить фабрику для вашей модели представления, но я уверен, что вы найдете множество примеров этого. Я предполагаю, что вы передаете идентификатор активности с помощью Intent, поэтому вы распаковываете это значение из пакета, переданного как параметр onCreate

1 голос
/ 17 июня 2020

Почему onChanged не запускается во второй раз, когда я вызываю Dao.findBy (friendId) (после вызова Dao.findBy (-1))

В вашем репозитории вы инициализируете перехватчик вашей базы данных с id = -1L. Теперь вы будете получать обновления только для сущностей с id = -1. И поля ID почти всегда - это ROWID баз данных, которые никогда не бывают отрицательными.

РЕШЕНИЕ: Теперь, как я понял из вашего вопроса, у вас будет одна и только одна запись в вашей таблице DBase. Это означает, что ожидаемое значение не зависит (и, следовательно, не может ) ни от какого параметра. Это просто реализовать:

Поскольку у вас будет только одна запись, которая не может зависеть ни от одного параметра, вам фактически не нужны никакие параметры для вашей функции findBy(long id). Просто удалите из запроса предложение WHERE :

@Query("SELECT * FROM friend_table LIMIT 1")
LiveData<FriendEntity> findBy();

Проблема с MutableLiveData заключается в том, что изменения в БД нельзя наблюдать напрямую Поскольку вы не предоставили реализации, я не могу догадаться, в чем может быть проблема. Однако это не должно вызывать проблем с правильной реализацией.


EDIT После получения отзыва о требовании правильный способ реализации:

  1. Dao :
    @Dao
    public interface MyDao {

        @Query("SELECT * FROM friend_table WHERE id=:id LIMIT 1")
        LiveData<FriendEntity> findBy(long id);
    }

Репозиторий: не нужен метод find (long id) . Измените способ предоставления LiveData . Как и в моем предыдущем ответе, не инициализируйте его с помощью id = -1L .
    public LiveData<FriendEntity> getFriendById(long id) {
        if(id != foundFriend.getId()) {
            //Background thread is not necessary for @Query annotated methods,
            //as Room framework handles those by itself
            foundFriend = dao.findBy(id);
        }
    return foundFriend;
}

ViewModel:

Этот шаг не является строго необходимым, но рекомендуется не предоставлять данные из репозитория непосредственно представлениям. И поскольку ваши результаты зависят от параметра id , используйте Transformations API, а именно: Transformations.switchMap () : https://developer.android.com/reference/androidx/lifecycle/Transformations (Я настоятельно рекомендую вам изучить и использовать этот API вместе с LiveData, где это применимо). Activity, Fragment et c) только один раз и может вызывать метод viewmodel updateFriend (long id) из вашего представления, когда вам требуется новый FriendEntity.

...