Android ViewModel LiveData观察

时间:2020-03-16 12:01:28

标签: android firebase-realtime-database mvvm android-livedata android-viewmodel

我遵守Android development team provided的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的工作,该活动显示了重复的书籍数据集。

Link to gif showing the outcome.

谁能找到问题所在?为什么onCreate第一次不初始化适配器?为什么我会得到重复的书籍清单?

Link to Git repository

1 个答案:

答案 0 :(得分:1)

让我们细分您所写的内容以及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) {

            }
        });
    }
}

最后是您的Activity的OnCreate()

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

另外,请查看Reading Data Once的Firebase实时数据库文档,因为我认为只取一次书就更适合您的用例了!