0

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);
AJW
  • 1,578
  • 3
  • 36
  • 77
  • Add break points here if (extras != null) here if (classname != null && classname.equals("fromMain")) and here if (card != null) and check the values when the card is null. it seems like the value of card id not being passed properly. – hfarhanahmed Aug 26 '19 at 05:03
  • @hfarhanahmed the card in the DetailActivity should never be null because the user is always clicking on a Card in the MainActivity. I confirm the cardId value from the Room database is correct using this Toast right after "card = card.getId()": "Toast.makeText(DetailActivity.this, "" + cardId, Toast.LENGTH_SHORT).show();" – AJW Aug 26 '19 at 05:23
  • The Id is correct and still it isn't showing the data? – hfarhanahmed Aug 26 '19 at 05:24
  • Yes. Therefore, I think it must be a problem with the ViewModel and its observer. – AJW Aug 26 '19 at 05:25
  • adapter2.setCardList(singleAdapterList); share the implementation of this function – hfarhanahmed Aug 26 '19 at 05:33
  • I added the implementation above in the Adapter file code. – AJW Aug 26 '19 at 05:37
  • isn't it a problem casting List to List ?? lets move this conversation to chat else the comments thread would be too long. – hfarhanahmed Aug 26 '19 at 05:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/198466/discussion-between-ajw-and-hfarhanahmed). – AJW Aug 26 '19 at 05:41
  • Not sure if this is related, but why are you doing `new ViewModelProvider(this).get(CardViewModel.class);` instead of using `ViewModelProviders`? For example: `ViewModelProviders.of(this).get(CardViewModel.class);` – Lyla Aug 28 '19 at 19:42
  • @Lyla Android Studio says "ViewModelProviders.of(this).get..." is deprecated – AJW Aug 28 '19 at 23:48
  • Oy, yep you're totally right - lemme keep looking – Lyla Aug 29 '19 at 17:45
  • When it fails to update the UI, can you confirm with break statements or logs whether or not the observer gets called? And if it does get called, what is in the singleAdapterList? – Lyla Aug 29 '19 at 18:04

1 Answers1

0

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.

AJW
  • 1,578
  • 3
  • 36
  • 77