简体   繁体   中英

Android ViewModel LiveData observe

I'm following ViewModel implementation guidelines as per Android development team provided . However, I'm getting unexpected results. I have been digging the internet but no luck.

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);
}
}

Expected result: App on launch opens MainActivity and displays the books inside a recycler view.

Actual Result: App on launch opens MainActivity displays no content. When clicked return app closes. When clicked on app icon to open it launches into displaying MainActivity showing duplicated dataset of books.

Link to gif showing the outcome.

Can anyone spot where is the problem? Why onCreate does not initialize the adapter on the first go? And why I get a duplicated list of books?

Link to Git repository

Let's breakdown what you've written and how LiveData works from your questions shall we?

First, Your adapter looks fine (mostly) and it isn't the problem here.

Let's look at the ViewModel and what's wrong here, read my comments inline for the solutions:

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) {

            }
        });
    }
}

and finally in your Activity's OnCreate()

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

Additionally, have a look at the Firebase Realtime DB Documentation for Reading Data Once since I believe that fits your usecase better of just fetching books once!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM