diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index b268ef3..723499e 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,6 +4,14 @@ diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModel.java b/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModel.java index 455723a..10e914d 100644 --- a/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModel.java +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModel.java @@ -146,17 +146,30 @@ MutableLiveData> userActivitiesLiveData = friendToActivitiesLiveData.get(userId); assert userActivitiesLiveData != null; + // 常に最新のアクティビティで上書きする。 + // + // 以前は「ID・テキスト・更新時刻が完全一致している場合は更新をスキップ」することで + // LiveData の無駄な更新を抑制していたが、バックエンドの実装やデータ形式の違いにより + // 期待どおりに差分検知ができていない可能性がある。 + // + // ここでは挙動の確実性を優先し、サーバから最新アクティビティが取得できたタイミングでは + // 必ず LiveData を更新するようにする。 + // (同じ内容であっても UI 側に再通知されるだけなので副作用は小さい) + List userActivities = userActivitiesLiveData.getValue(); if (userActivities == null || userActivities.isEmpty()) { - userActivitiesLiveData.postValue(List.of(latestActivity)); + // 初回またはこれまでアクティビティが無い場合はそのまま反映 + Log.d(ActivityViewModel.class.getSimpleName(), "updateFriendToActivitiesLiveData: initial set for user=" + userId + + ", activityId=" + latestActivity.getActivityId() + ", text=" + latestActivity.getText()); } else { - if (userActivities.get(0).getActivityId().equals(latestActivity.getActivityId())) { - return; - } - - userActivitiesLiveData.postValue(List.of(latestActivity)); + Activity previousLatest = userActivities.get(0); + Log.d(ActivityViewModel.class.getSimpleName(), "updateFriendToActivitiesLiveData: overwrite for user=" + userId + + " from activityId=" + previousLatest.getActivityId() + " to activityId=" + latestActivity.getActivityId()); } + + // 最新アクティビティのみを保持するリストとして常に上書きする + userActivitiesLiveData.postValue(List.of(latestActivity)); } /** diff --git a/app/src/main/java/com/example/tampopo_client/views/MainActivity.java b/app/src/main/java/com/example/tampopo_client/views/MainActivity.java index 9911876..24ec48b 100644 --- a/app/src/main/java/com/example/tampopo_client/views/MainActivity.java +++ b/app/src/main/java/com/example/tampopo_client/views/MainActivity.java @@ -388,118 +388,86 @@ i = 0; for (String friendId : friends) { - FriendIconView userView = userViews.get(friendId); - //userViews.put(friendId, null); - MutableLiveData> activitiesLiveData = activityViewModel.getActivitiesLiveDataFromUserId(friendId); + // lambda から参照するため final 変数に退避 + final String currentFriendId = friendId; -// for (String friendId : sortedFriendUserIds) { -// FriendIconView userView = userViews.get(friendId); -// MutableLiveData> activitiesLiveData = activityViewModel.getActivitiesLiveDataFromUserId(friendId); - //nattyもしnullだったらって処理入れた気がする -// if (userView == null) { -// userView = new FriendIconView( -// MainActivity.this, -// friendId, -// userViewModel.getNickname(friendId), -// chatViewModel -// ); -// userViews.put(friendId, userView); -// } - // 新しいユーザなので、アイコン+コメントを作成 - //FriendIconView container = new FriendIconView(this); + // すでに View が存在する場合は再利用し、新規に作らない + FriendIconView container = userViews.get(currentFriendId); + if (container == null) { + // 初回のみ FriendIconView を生成して配置する + container = new FriendIconView(MainActivity.this, currentFriendId, userViewModel.getNickname(currentFriendId), chatViewModel); + container.setPadding(16, 16, 16, 16); + container.setId(View.generateViewId()); - new Thread(new Runnable() { - @Override - public void run() { - FriendIconView container = new FriendIconView(MainActivity.this, friendId, userViewModel.getNickname(friendId), chatViewModel); + ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams( + ConstraintLayout.LayoutParams.WRAP_CONTENT, + ConstraintLayout.LayoutParams.WRAP_CONTENT + ); + container.setLayoutParams(params); + layout.addView(container); - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { - @Override - public void run() { - container.setPadding(16, 16, 16, 16); - container.setId(View.generateViewId()); + // Mapに登録、画面に追加 + userViews.put(currentFriendId, container); -// // ユーザのアイコン(固定) -// ShapeableImageView iconView = new ShapeableImageView(MainActivity.this); -// iconView.setLayoutParams(new LinearLayout.LayoutParams(100, 100)); -// iconView.setScaleType(ImageView.ScaleType.CENTER_CROP); -// iconView.setStrokeColor(ContextCompat.getColorStateList(MainActivity.this, R.color.red)); -// iconView.setStrokeWidth(2f); -// iconView.setShapeAppearanceModel( -// iconView.getShapeAppearanceModel().toBuilder() -// .setAllCornerSizes(50) // 丸く -// .build() -// ); - ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams( - ConstraintLayout.LayoutParams.WRAP_CONTENT, - ConstraintLayout.LayoutParams.WRAP_CONTENT - ); - container.setLayoutParams(params); - layout.addView(container); -// -// // ユーザIDに応じてアイコンリソースを決定(仮にハードコード or マッピング) -// iconView.setImageResource(getUserIconResource(friendId)); // ←ここがポイント + // 位置決め + ConstraintSet set = new ConstraintSet(); + set.clone(layout); + int marginTopInPx = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + marginTopInDp[i], + getResources().getDisplayMetrics() + ); + int marginStartInPx = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + marginStartInDp[i], + getResources().getDisplayMetrics() + ); - // Mapに登録、画面に追加 - userViews.put(friendId, container); -// userView = container; -// messageList.addView(container); + set.connect(container.getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, marginTopInPx); + set.connect(container.getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, marginStartInPx); - ConstraintSet set = new ConstraintSet(); - set.clone(layout); - int marginTopInPx = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - marginTopInDp[i], - getResources().getDisplayMetrics() - ); - int marginStartInPx = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - marginStartInDp[i], - getResources().getDisplayMetrics() - ); + set.applyTo(layout); - set.connect(container.getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, marginTopInPx); - set.connect(container.getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, marginStartInPx); - - set.applyTo(layout); - - if (i < 6) { - i++; - } - - // TODO: CHANGE - activitiesLiveData.observe(MainActivity.this, activities -> { - if (activities == null || activities.isEmpty()) return; - - Activity latest = activities.get(activities.size() - 1); - - // UIスレッドで安全に更新 - FriendIconView friendView = userViews.get(friendId); - if (friendView != null) { - friendView.setComment(latest.getText()); - Log.d("ActivityUpdate", friendId + " のコメントを更新: " + latest.getText()); - } - }); - - List sortedFriendUserIds = activityViewModel.getSortedFriendUserIds(); - int size = sortedFriendUserIds.size(); - List latestSix = sortedFriendUserIds.subList(Math.max(size - 6, 0), size); - - synchronized (recentUpdatedFriends) { - if (latestSix.contains(friendId)) { - recentUpdatedFriends.remove(friendId); - recentUpdatedFriends.add(0, friendId); - if (recentUpdatedFriends.size() > 6) { - recentUpdatedFriends.remove(recentUpdatedFriends.size() - 1); - } - } - } - } - }); + if (i < 6) { + i++; } - }).start(); + } + + // LiveData の取得は View の再利用/新規作成に関わらず行う + final FriendIconView finalContainer = container; + + MutableLiveData> activitiesLiveData = activityViewModel.getActivitiesLiveDataFromUserId(currentFriendId); + + // FriendIconView 自身が持つアクティビティ監視用 Observer を利用して + // 各フレンドの最新アクティビティが更新されたタイミングで + // 吹き出しコメントが自動的に変わるようにする + activitiesLiveData.observe(MainActivity.this, finalContainer.getActivitiesObserver()); + + // 念のため、MainActivity 側でも直接 FriendIconView のコメントを更新する + // (getActivitiesObserver() が正しく動作していれば二重更新になるが、副作用はない) + activitiesLiveData.observe(MainActivity.this, activities -> { + if (activities == null || activities.isEmpty()) return; + + // ActivityViewModel 側では常に最新 1 件のみを保持しているため index 0 を参照する + Activity latest = activities.get(0); + finalContainer.setComment(latest.getText()); + Log.d("ActivityUpdate", currentFriendId + " のコメントを MainActivity から更新: " + latest.getText()); + }); + + List sortedFriendUserIds = activityViewModel.getSortedFriendUserIds(); + int size = sortedFriendUserIds.size(); + List latestSix = sortedFriendUserIds.subList(Math.max(size - 6, 0), size); + + synchronized (recentUpdatedFriends) { + if (latestSix.contains(currentFriendId)) { + recentUpdatedFriends.remove(currentFriendId); + recentUpdatedFriends.add(0, currentFriendId); + if (recentUpdatedFriends.size() > 6) { + recentUpdatedFriends.remove(recentUpdatedFriends.size() - 1); + } + } + } } } }