首页 > 解决方案 > Android ViewModel LiveData 观察

问题描述

我正在按照Android 开发团队提供的 ViewModel 实施指南进行操作。但是,我得到了意想不到的结果。我一直在挖掘互联网,但没有运气。

BookMarketPojo.java

class BookMarketPojo {
private String bookTitle;
private String bookAuthor;
private String bookDescription;
private String bookPictureUrl;
private String ownerId;
private boolean sold;

public BookMarketPojo() {
}

public BookMarketPojo(String bookTitle, String bookAuthor, String bookDescription, String bookPictureUrl, String ownerId, boolean sold) {
    this.bookTitle = bookTitle;
    this.bookAuthor = bookAuthor;
    this.bookDescription = bookDescription;
    this.bookPictureUrl = bookPictureUrl;
    this.ownerId = ownerId;
    this.sold = sold;
}

// getters and setters
}

BookViewModel.java

public class BookViewModel extends ViewModel {
private List<BookMarketPojo> booksList = new ArrayList<>();

private final String NODE_NAME = "_books_for_sale_";

private FirebaseDatabase db = FirebaseDatabase.getInstance();

public MutableLiveData<List<BookMarketPojo>> getData() {
    fetchData();
    MutableLiveData<List<BookMarketPojo>> data = new MutableLiveData<>();
    data.setValue(booksList);
    return data;
}

private void fetchData() {
    db.getReference(NODE_NAME).addChildEventListener(new ChildEventListener() {
        @Override
        public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
            booksList.add(dataSnapshot.getValue(BookMarketPojo.class));
        }

        @Override
        public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

        }

        @Override
        public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {

        }

        @Override
        public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {

        }
    });
}
}

BookMarketAdapter.java

public class BookMarketAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private Context context;
private List<BookMarketPojo> bookMarketPojos;

public BookMarketAdapter(Context context, List<BookMarketPojo> bookMarketPojos) {
    this.context = context;
    this.bookMarketPojos = bookMarketPojos;
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(context).inflate(R.layout.book_market_row, parent, false);
    return new ViewHolder(itemView);
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
    final BookMarketPojo book = bookMarketPojos.get(position);

    ((ViewHolder) viewHolder).title.setText(book.getBookTitle());
    ((ViewHolder) viewHolder).author.setText(book.getBookAuthor());
    ((ViewHolder) viewHolder).description.setText(book.getBookDescription());
    Picasso.get().load(book.getBookPictureUrl()).into(((ViewHolder) viewHolder).bookImg);
}

@Override
public int getItemCount() {
    return bookMarketPojos.size();
}

private class ViewHolder extends RecyclerView.ViewHolder {
    TextView title, author, description;
    ImageView bookImg;

    public ViewHolder(View convertView) {
        super(convertView);
        title = convertView.findViewById(R.id.tv_title);
        author = convertView.findViewById(R.id.tv_author);
        description = convertView.findViewById(R.id.tv_description);
        bookImg = convertView.findViewById(R.id.iv_bookImg);
    }
}
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MarketActivity";

private BookMarketAdapter myAdapter;
private RecyclerView recyclerView;

private BookViewModel bookViewModel;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    recyclerView = findViewById(R.id.rv_bookMarket);

    bookViewModel = new ViewModelProvider(this).get(BookViewModel.class);

    bookViewModel.getData().observe(this, books -> {
        myAdapter.notifyDataSetChanged();
    });

    initRecyclerView();

}

public void initRecyclerView(){
    myAdapter = new BookMarketAdapter(this, bookViewModel.getData().getValue());
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setAdapter(myAdapter);
}
}

预期结果:应用程序在启动时打开 MainActivity 并在回收站视图中显示书籍。

实际结果:应用程序在启动时打开 MainActivity 不显示任何内容。当点击返回应用程序关闭。单击应用程序图标打开时,它会启动并显示 MainActivity,显示重复的书籍数据集。

链接到显示结果的 gif。

谁能发现问题出在哪里?为什么 onCreate 不会在第一次初始化适配器?为什么我会得到重复的书单?

链接到 Git 存储库

标签: androidfirebase-realtime-databasemvvmandroid-livedataandroid-viewmodel

解决方案


让我们根据您的问题来分解您所写的内容以及 LiveData 的工作原理,好吗?

首先,您的适配器看起来不错(大部分情况下),这不是问题所在。

让我们看看 ViewModel 以及这里出了什么问题,请阅读我的内联评论以获取解决方案:

public class BookViewModel extends ViewModel {

    private List<BookMarketPojo> booksList = new ArrayList<>();

    private final String NODE_NAME = "_books_for_sale_";

    private FirebaseDatabase db = FirebaseDatabase.getInstance();

    /** 
    * The purpose of livedata is to simply emit events. 
    * Therefore this can be simply written as [note the general convention used in naming]
    */
    public MutableLiveData<List<BookMarketPojo>> onBookListUpdated = MutableLiveData<List<BookMarketPojo>>();

    /** 
    * The problem with this code, is that when you set the value initially, you only set an empty arraylist. 
    * Remember, your LiveData is only a way to emit changes to a data (the data here being booksList) with the safetynet of LifeCycle Awareness
    * therefore, to fetch the data, we only need to do this. 
    */
    public void getData() {
        fetchData()
    }

    private void fetchData() {
        db.getReference(NODE_NAME).addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

                //To remove duplicates, make sure the book isn't already present in the list. Exercise is left to you
                booksList.add(dataSnapshot.getValue(BookMarketPojo.class));

                //This will now trigger the livedata, with the list of the books.
                onBookListUpdated.setValue(booksList)

            }

            @Override
            public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

            }

            @Override
            public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {

            }

            @Override
            public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });
    }
}

最后在你的活动的 OnCreate()

 bookViewModel.onBookListUpdated.observe(this, books -> {
        myAdapter.notifyDataSetChanged();
   });

此外,请查看 Firebase Realtime DB Documentation for Reading Data Once,因为我相信它更适合您的用例,而不是只取书一次!


推荐阅读