Android: RecyclerView's CardView Not Showing E

2020-02-15 08:16发布

问题:

I have a RecyclerView in MainActivity that shows a list of CardViews and that is working properly. A click on the CardView finishes the RecyclerView Activity and launches a Detail Activity that shows the clicked on CardView in a new RecyclerView list. The Detail Activity is used only to show that single CardView in a RecyclerView (I do this so I can use RecyclerView's ItemTouchHelper.SimpleCallback code on the CardView for easy left swipe for the user).

Here is the problem: I hit the left caret on the Detail Activity's Toolbar to return to the MainActivity. Then a click on the exact same CardView brings the user back to the Detail Activity. But this time only the background View (a border) is showing. The view of the CardView and its database data is completely missing.

The error appears to happen randomly. I can click to go from the MainActivity to the Detail Activity back and forth 5 times successfully and then on the sixth try, no CardView will show in the Detail Activity. Or I'll click two times successfully and then the third time, the CardView in the Detail Activity will not show. Note the left caret click in Detail Activity uses onBackPressed() so the Detail Activity finishes. So I don't think there should be any backstack issues. I also tried to adjust the xml height for the CardView to match_parent rather than wrap_content but no luck. The Detail Activity's ViewModel to Repository to Dao returns a List wrapped in LiveData. Perhaps there is an observer problem with the ViewModel, but I thought the observer gets removed/destroyed when the Detail Activity is destroyed? What am I missing here?

Adapter

...
itemHolder.cardView.setOnClickListener(view -> {

Card adapterItem= TodosAdapter.this.getItem(itemHolder.getAdapterPosition()); 
            int adapPos = itemHolder.getAdapterPosition();
            if (adapPos !=RecyclerView.NO_POSITION) {
                onItemClick(adapPos, adapterItem);
            }
        });

MainActivity

...
public void onItemClick(int clickPos, Card cardFromClick) {
    Intent intent = new Intent(MainActivity.this, DetailActivity.class);
        intent.putExtra("TAG","fromMain");
        intent.putExtra("itemFromMain", cardFromClick);
        startActivity(intent);
        finish();    

DetailActivity

...
public class DetailActivity extends AppCompatActivity {

private int cardId = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_details);

    // Get a new or existing ViewModel from the ViewModelProvider.
    detsViewModel = new ViewModelProvider(this).get(CardViewModel.class);

    Toolbar toolbar = findViewById(R.id.toolbar);        
        // The left caret is for Up navigation to the previous activity
        // for OS versions 4.0 and earlier.
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        toolbar.setNavigationIcon(R.drawable.ic_action_previous_item);            
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                onBackPressed();
            }
        });            
    }

    Intent intent = getIntent();
    Bundle extras = intent.getExtras(); 

    if (extras != null) {
        String classname = extras.getString("TAG");

        // The user clicked on a Card in the MainActivity
        if (classname != null && classname.equals("fromMain")) {
            card = extras.getParcelable("itemFromMain");
            if (card != null) {
                cardId = card.getId(); // card data is stored in Room database.
            }
        }
    }

    detsViewModel.getSingleCard(cardId).observe(this, singleAdapterList -> {

        adapter2.setCardList(singleAdapterList);            
    });
}

activity_details.xml

...
<RelativeLayout

xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
tools:context=".DetailsActivity"  >

<include
    android:id="@+id/toolbar"
    layout="@layout/toolbar" >
</include>

<RelativeLayout
    android:id="@+id/todoListLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_below="@+id/toolbar"  >

    <TextView
        android:id="@+id/Card"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:text="Card"
        android:textStyle="bold"
        android:textColor="@color/text_primary"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:layout_centerHorizontal="true"  />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/details_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_below="@+id/Card"
        android:scrollbars="vertical"  />

    <TextView
        android:id="@+id/skytext5"
        android:text="Cards"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/details_recyclerview"
                    android:background="@color/colorPrimary"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:clickable="true"
        android:focusable="true"  />        

</RelativeLayout>

DetailsAdapter

...
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    View itemView = LayoutInflater.from(mContext).inflate(R.layout.details_list_item, parent, false);
}

private List<Card> oneCardList

public void setCardList(List<Card> singleCardList) {

    if (oneCardList != null) {
        oneCardList.clear();
        this.oneCardList = singleCardList;
    } else {
        // First initialization
        this.oneCardList = singleCardList;
    }
}

details_list_item.xml

...
<FrameLayout

xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/detsinglecard_view"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical"
android:foreground="?android:attr/selectableItemBackground"
android:background="#FFFFFF"
tools:context=".DetailActivity">

<RelativeLayout
    android:id="@+id/view_background2"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    ...
</RelativeLayout>

<RelativeLayout
    android:id="@+id/view_foreground2"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:background="@color/colorFlLabelFinal"  >

        <androidx.cardview.widget.CardView
            android:id="@+id/cardview_dets"
            android:layout_height="match_parent"
            android:layout_width="match_parent"
            ...
} 

ViewModel

...
LiveData<List<Card>> getSingleCard(int cardId) {
    return repository.getSingleCard(cardId);
}

Repository

...
 public LiveData<List<Card>> getSingleCard(int cardId) {
    return quickcardDao.getSingleCard(cardId);
}  

Dao

...
@Query("SELECT * FROM cards WHERE cardId = :cardId LIMIT 1")
LiveData<List<Card>> getSingleCard(int cardId);

回答1:

So if the data does not change then going back to the same DetailActivity will not refresh the View. The answer was to re-use the LiveData (rather than re-loading the LiveData again from the database) if the data has not changed. See the Android Developers Architecture Components guide for ViewModel, "Implement a ViewModel" section for the "loadUsers()" example that solved my problem: https://developer.android.com/topic/libraries/architecture/viewmodel.