To summarize, I'm building a social media app that displays:
- A Newsfeed that shows posts from all user profiles in a given account
- A Timeline that shows posts from a specific user profile in the account
I've built a custom BaseAdapter to populate a custom cell within each ListView. The Newsfeed ListView (that populates posts from all users on the account) is populating correctly.
The Timeline ListView (that populates posts from one profile on the account) is not showing. I've set breakpoints to ensure that my ArrayList is not null or empty when populating the Timeline ListView. In addition, breakpoints verify that my custom adapter is, in fact, pulling data from the ArrayList and inflating cells. However, the ListView is simply not visible.
Here is the layout file for my Newsfeed fragment (that is working correctly):
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorSkyPrimary">
<android.support.constraint.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:elevation="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<EditText
android:id="@+id/input_post"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:backgroundTint="@color/colorSkyPrimary"
android:ems="10"
android:gravity="start|top"
android:hint="@string/what_s_going_on"
android:importantForAutofill="no"
android:inputType="textMultiLine"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:targetApi="o" />
<ImageButton
android:id="@+id/button_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:backgroundTint="@android:color/white"
android:contentDescription="@string/camera_button"
app:layout_constraintBottom_toBottomOf="@+id/input_post"
app:layout_constraintEnd_toEndOf="@+id/input_post"
app:srcCompat="@drawable/camera_icon" />
<Button
android:id="@+id/button_cancel"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginTop="8dp"
android:text="@android:string/cancel"
android:textColor="@color/colorGrassPrimary"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/post_image" />
<Button
android:id="@+id/button_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="48dp"
android:backgroundTint="@color/colorGrassPrimary"
android:text="@string/update"
android:textColor="@color/colorButtonText"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/post_image" />
<ImageView
android:id="@+id/post_image"
android:layout_width="0dp"
android:layout_height="300dp"
android:contentDescription="@string/post_image"
android:scaleType="fitCenter"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/input_post"
tools:srcCompat="@tools:sample/avatars" />
</android.support.constraint.ConstraintLayout>
<ListView
android:id="@+id/list_newsfeed"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/picker_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/constraintLayout"
app:layout_constraintVertical_bias="0.0" />
<android.support.constraint.ConstraintLayout
android:id="@+id/picker_image"
android:layout_width="0dp"
android:layout_height="75dp"
android:background="@android:color/white"
android:elevation="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<android.support.constraint.Guideline
android:id="@+id/guideline14"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<ImageButton
android:id="@+id/button_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="8dp"
android:background="@android:color/transparent"
android:contentDescription="@string/camera_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline14"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/camera_icon_large" />
<ImageButton
android:id="@+id/button_gallery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@android:color/transparent"
android:contentDescription="@string/gallery_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline14"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.375"
app:srcCompat="@drawable/gallery_icon" />
</android.support.constraint.ConstraintLayout>
Here is the layout file for my Profile fragment (in which the ListView is not showing at runtime)
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorSkyPrimary">
<android.support.constraint.ConstraintLayout
android:id="@+id/constraintLayout4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:elevation="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/profile_photo"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:src="@drawable/male_icon_large"
app:civ_border_color="@android:color/transparent"
app:civ_border_width="2dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/display_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:text="@string/brelynn_mack"
android:textAlignment="viewStart"
android:textColor="@color/colorTextDark"
android:textSize="18sp"
app:layout_constraintEnd_toStartOf="@+id/button_delete_profile"
app:layout_constraintStart_toEndOf="@+id/profile_photo"
app:layout_constraintTop_toTopOf="@+id/profile_photo" />
<TextView
android:id="@+id/display_timestamp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="@string/may_14_2019_9_59_pm"
android:textAlignment="viewStart"
android:textColor="@color/colorTextLight"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/profile_photo"
app:layout_constraintTop_toBottomOf="@+id/display_name" />
<TextView
android:id="@+id/display_last_location"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="@string/_8741_grouse_run_lane_28314"
android:textAlignment="viewStart"
android:textColor="@color/colorTextPrimary"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/profile_photo"
app:layout_constraintTop_toBottomOf="@+id/display_timestamp" />
<ImageButton
android:id="@+id/button_delete_profile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@android:color/white"
android:contentDescription="@string/trash_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/trash_icon" />
<ImageButton
android:id="@+id/button_photo"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="16dp"
android:background="@android:color/white"
android:contentDescription="@string/camera_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/profile_photo"
app:layout_constraintTop_toBottomOf="@+id/profile_photo"
app:srcCompat="@drawable/camera_icon" />
<ImageButton
android:id="@+id/button_family"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:background="@android:color/white"
android:contentDescription="@string/family_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/family_icon_small" />
<ImageButton
android:id="@+id/button_gallery"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="8dp"
android:background="@android:color/transparent"
android:contentDescription="@string/gallery_button"
android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="@+id/profile_photo"
app:layout_constraintTop_toBottomOf="@+id/profile_photo"
app:srcCompat="@drawable/gallery_icon" />
</android.support.constraint.ConstraintLayout>
<ListView
android:id="@+id/list_posts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/constraintLayout4" />
Here is my custom adapter:
public class NewsfeedAdapter extends BaseAdapter {
// Class properties
private static final String TAG = "NewsfeedAdapter";
public static final String EXTRA_POSTS = "extra_posts";
public static final String EXTRA_POSITION = "extra_position";
private final Context context;
ArrayList<Post> posts;
Account account;
// Constructor
public NewsfeedAdapter(Context context, ArrayList<Post> posts, Account account) {
this.context = context;
this.posts = posts;
this.account = account;
}
// System generated methods
@Override
public int getCount() {
return posts.size();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return posts.get(position);
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final Post post = posts.get(position);
if(convertView == null) {
LayoutInflater layoutInflater = LayoutInflater.from(context);
convertView = layoutInflater.inflate(R.layout.cell_newsfeed, null);
}
TextView profileNameDisplay = convertView.findViewById(R.id.display_profile_name);
String name = post.getPosterName() + " " + account.getFamilyName();
profileNameDisplay.setText(name);
TextView timestampDisplay = convertView.findViewById(R.id.display_timestamp);
SimpleDateFormat dateFormat = new SimpleDateFormat("MMMM dd, yyyy @ hh:mm a", Locale.getDefault());
String timestamp = dateFormat.format(post.getTimeStamp());
timestampDisplay.setText(timestamp);
TextView postMessageDisplay = convertView.findViewById(R.id.display_post_message);
postMessageDisplay.setText(post.getPostMessage());
if (post.getHasImage()) {
AccountUtils.loadProfilePhoto(context, convertView, post.getPosterId());
}
else {
ImageView postImage = convertView.findViewById(R.id.display_post_image);
postImage.setVisibility(View.GONE);
}
PostUtils.loadPostImage(convertView, post.getPostId());
ImageButton deleteButton = convertView.findViewById(R.id.button_delete_post);
ImageButton editButton = convertView.findViewById(R.id.button_edit_post);
toggleButtons(post, editButton, deleteButton);
deleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
alertBuilder.setTitle(context.getString(R.string.delete_post));
alertBuilder.setMessage(context.getString(R.string.delete_post_message));
alertBuilder.setPositiveButton(context.getString(R.string.delete), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
posts.remove(position);
PostUtils.deletePost(context, post.getPostId(), post.getPosterId());
notifyDataSetChanged();
PostUtils.listenForNews(context);
}
});
alertBuilder.setNegativeButton(context.getString(R.string.cancel), null);
AlertDialog alert = alertBuilder.create();
alert.show();
}
});
editButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent editIntent = new Intent(context, EditPostActivity.class);
editIntent.putExtra(EXTRA_POSTS, posts);
editIntent.putExtra(EXTRA_POSITION, position);
context.startActivity(editIntent);
}
});
return convertView;
}
// Custom methods
private void toggleButtons(Post post, ImageButton editButton, ImageButton deleteButton) {
long twoMinutes = System.currentTimeMillis() - (2 * 60 * 1000);
long fiveMinutes = System.currentTimeMillis() - (5 * 60 * 1000);
if (post.getTimeStamp().getTime() < fiveMinutes) {
editButton.setVisibility(View.GONE);
}
else {
editButton.setVisibility(View.VISIBLE);
}
if (post.getTimeStamp().getTime() < twoMinutes) {
deleteButton.setVisibility(View.GONE);
}
else {
deleteButton.setVisibility(View.VISIBLE);
}
}
Here are the lifecycle methods from the Newsfeed fragment that load my data and set the adapter:
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_newsfeed, container, false);
PostUtils.listenForNews(getActivity());
mPosts = PostUtils.loadNewsfeed(getActivity());
mNewsfeed = view.findViewById(R.id.list_newsfeed);
mImagePicker = view.findViewById(R.id.picker_image);
mPhotoView = view.findViewById(R.id.post_image);
AccountUtils.listenForUpdates(getActivity());
setClickListener(view);
setFocusListener(view);
return view;
}
@Override
public void onResume() {
super.onResume();
mPosts = PostUtils.loadNewsfeed(getActivity());
Account account = AccountUtils.loadAccount(getActivity());
mNewsfeedAdapter = new NewsfeedAdapter(getActivity(), mPosts, account);
mNewsfeed.setAdapter(mNewsfeedAdapter);
}
Here are the lifecycle methods from my Profile fragment where the same functionality should work for a slightly different set of data:
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_profile, container, false);
try {
if(getActivity().getIntent() != null && getActivity().getIntent().getAction().equals(FamilyProfileFragment.ACTION_EDIT_PROFILE)) {
mEditingSelf = false;
mIsParent = true;
mProfile = (Profile) getActivity().getIntent().getSerializableExtra(FamilyProfileFragment.EXTRA_PROFILE);
String selectedName = mProfile.getFirstName();
String loadedName = AccountUtils.loadProfile(getActivity()).getFirstName();
if(selectedName.equals(loadedName)) {
mEditingSelf = true;
}
}
else {
mEditingSelf = true;
mProfile = AccountUtils.loadProfile(getActivity());
if(mProfile instanceof Parent) {
mIsParent = true;
}
else {
mIsParent = false;
}
}
}
catch (Exception e) {
e.printStackTrace();
mEditingSelf = true;
mProfile = AccountUtils.loadProfile(getActivity());
if(mProfile instanceof Parent) {
mIsParent = true;
}
else {
mIsParent = false;
}
}
mAccount = AccountUtils.loadAccount(getActivity());
AccountUtils.loadProfilePhoto(getActivity(), view, mProfile.getProfileId());
mPhotoView = view.findViewById(R.id.profile_photo);
PostUtils.listenForTimeline(getActivity(), mProfile);
mPosts = PostUtils.loadTimeline(getActivity());
mTimeline = view.findViewById(R.id.list_posts);
setClickListener(view);
setTextDisplay(view);
populateProfile(view);
return view;
}
@Override
public void onResume() {
super.onResume();
mPosts = PostUtils.loadTimeline(getActivity());
mNewsfeedAdapter = new NewsfeedAdapter(getActivity(), mPosts, mAccount);
mTimeline.setAdapter(mNewsfeedAdapter);
}