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 3876ffd..31b884f 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 @@ -4,7 +4,6 @@ import androidx.annotation.NonNull; import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; import com.example.tampopo_client.models.Activity; import com.example.tampopo_client.resources.ActivitiesResource; @@ -13,10 +12,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import retrofit2.Call; import retrofit2.Callback; @@ -32,20 +27,20 @@ * @author Shohei Yamagiwa * @implNote Repositoryは作成せずに、すべてViewModelで処理する */ -public class ActivityViewModel extends ViewModel { +public class ActivityViewModel extends RealTimeViewModel { private final ActivitiesResource activitiesResource; private final UserResource userResource; private final MutableLiveData> activitiesLiveData; // key=userId, value=activity private final MutableLiveData> friendUserIdsLiveData; - private final ScheduledExecutorService fetchActivitiesTaskScheduler; - private ScheduledFuture fetchActivitiesTask; + private final String userId; + private final String token; - private final ScheduledExecutorService fetchFriendIdsTaskScheduler; - private ScheduledFuture fetchFriendIdsTask; + public ActivityViewModel(String userId, String token) { + this.userId = userId; + this.token = token; - public ActivityViewModel() { // Retrofitの初期化 final Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://nitta-lab-www.is.konan-u.ac.jp/tampopo/") @@ -57,68 +52,30 @@ // LiveDataの初期化 activitiesLiveData = new MutableLiveData<>(Map.of()); friendUserIdsLiveData = new MutableLiveData<>(List.of()); - - // スケジューラーの作成 - fetchActivitiesTaskScheduler = Executors.newSingleThreadScheduledExecutor(); - fetchFriendIdsTaskScheduler = Executors.newSingleThreadScheduledExecutor(); } @Override - protected void onCleared() { - super.onCleared(); - - // ViewModelの破棄時にタスクを即停止する - if (fetchActivitiesTaskScheduler != null) { - fetchActivitiesTaskScheduler.shutdownNow(); - } - if (fetchFriendIdsTaskScheduler != null) { - fetchFriendIdsTaskScheduler.shutdownNow(); - } - } - - /** - * 定期的に最新のアクティビティ一覧を取得・更新する - */ - public void startFetchingLatestActivities() { - if (fetchActivitiesTask != null && !fetchActivitiesTask.isDone()) { - return; - } - - final Runnable task = () -> { - if (!friendUserIdsLiveData.isInitialized()) { + public Runnable onUpdate() { + return () -> { + if (activitiesLiveData == null || friendUserIdsLiveData == null) { return; } - if (friendUserIdsLiveData.getValue() == null) { - return; + + // 最新のアクティビティを取得して更新する + if (friendUserIdsLiveData.isInitialized() && friendUserIdsLiveData.getValue() != null) { + for (String userId : friendUserIdsLiveData.getValue()) { + pullLatestActivity(userId); + } } - for (String userId : friendUserIdsLiveData.getValue()) { - pullLatestActivity(userId); + + // 最新のフレンドのユーザーIDを取得して更新する + if (friendUserIdsLiveData.isInitialized()) { + pullLatestFriendUserIds(userId, token); } + + // Logging + Log.d(ActivityViewModel.class.getSimpleName(), "Polling data from the server."); }; - fetchActivitiesTask = fetchActivitiesTaskScheduler.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS); - } - - /** - * 定期的に最新のフレンド一覧を取得・更新する - * - * @param userId ユーザーID - * @param token ユーザーの認証用トークン - */ - public void startFetchingLatestFriends(String userId, String token) { - if (fetchFriendIdsTask != null && !fetchFriendIdsTask.isDone()) { - return; - } - - final Runnable task = () -> { - if (!friendUserIdsLiveData.isInitialized()) { - return; - } - - pullLatestFriendUserIds(userId, token); - - Log.d(ActivityViewModel.class.getSimpleName(), "Polling friends data from the server."); - }; - fetchFriendIdsTask = fetchFriendIdsTaskScheduler.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS); } public void createActivity(String userId, String token, String newActivity) { @@ -232,4 +189,12 @@ public MutableLiveData> getFriendUserIdsLiveData() { return friendUserIdsLiveData; } + + public String getUserId() { + return userId; + } + + public String getToken() { + return token; + } } diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModelFactory.java b/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModelFactory.java new file mode 100644 index 0000000..f52c8bf --- /dev/null +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModelFactory.java @@ -0,0 +1,36 @@ +package com.example.tampopo_client.viewmodels; + +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import java.lang.reflect.InvocationTargetException; + +/** + * ViewModelのライフサイクルの関係で、インスタンスを生成するためのFactoryクラスが必要 + * + * @author Shohei Yamagiwa + */ +public class ActivityViewModelFactory implements ViewModelProvider.Factory { + private final String userId; + private final String token; + + public ActivityViewModelFactory(String userId, String token) { + this.userId = userId; + this.token = token; + } + + @NonNull + @Override + public T create(@NonNull Class modelClass) { + if (modelClass.isAssignableFrom(ActivityViewModel.class)) { + try { + return modelClass.getConstructor(String.class, String.class).newInstance(userId, token); + } catch (InvocationTargetException | IllegalAccessException | InstantiationException | + NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + throw new IllegalStateException("作成するViewModelが異なります。"); + } +} diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/RealTimeViewModel.java b/app/src/main/java/com/example/tampopo_client/viewmodels/RealTimeViewModel.java new file mode 100644 index 0000000..c56ac08 --- /dev/null +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/RealTimeViewModel.java @@ -0,0 +1,52 @@ +package com.example.tampopo_client.viewmodels; + +import androidx.lifecycle.ViewModel; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * {@code onUpdate()}が1秒毎に呼び出されます。 + * + * @author Shohei Yamagiwa + */ +public abstract class RealTimeViewModel extends ViewModel { + private final ScheduledExecutorService updateScheduler; + private ScheduledFuture updateTask; + + public RealTimeViewModel() { + updateScheduler = Executors.newSingleThreadScheduledExecutor(); + } + + public abstract Runnable onUpdate(); + + @Override + protected void onCleared() { + super.onCleared(); + + stopUpdating(); + } + + /** + * 定期的に{@code onUpdate()}を呼び出す + * + * @param delay 定期実行の間隔(秒) + */ + public void startUpdating(long delay) { + if (updateTask != null && !updateTask.isDone()) { + return; + } + updateTask = updateScheduler.scheduleWithFixedDelay(this.onUpdate(), 0, delay, TimeUnit.SECONDS); + } + + /** + * 定期実行を停止する + */ + public void stopUpdating() { + if (updateScheduler != null) { + updateScheduler.shutdownNow(); + } + } +} 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 14bf304..40c8179 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 @@ -26,9 +26,8 @@ import com.example.tampopo_client.Tampopo; import com.example.tampopo_client.models.Activity; import com.example.tampopo_client.viewmodels.ActivityViewModel; -import com.example.tampopo_client.viewmodels.UserViewModel; +import com.example.tampopo_client.viewmodels.ActivityViewModelFactory; -import java.util.List; import java.util.Map; public class MainActivity extends AppCompatActivity { @@ -66,11 +65,13 @@ } }); - //activityViewModelを宣言する - activityViewModel = new ViewModelProvider(this).get(ActivityViewModel.class); //tampopoを宣言する tampopo = (Tampopo) getApplication(); + //activityViewModelを宣言する + ActivityViewModelFactory factory = new ActivityViewModelFactory(tampopo.getUserId(), tampopo.getToken()); // Factoryを使って、引数をコンストラクタにわたしつつViewModelを作成 + activityViewModel = new ViewModelProvider(this, factory).get(ActivityViewModel.class); + // MutableLiveData>friendUserIdsLiveDate = activityViewModel.getFriendUserIdsLiveData(); // friendUserIdsLiveDate.observe(this, new Observer>() { @@ -83,10 +84,10 @@ // } // }); - MutableLiveData> activitiesLiveData = activityViewModel.getActivitiesLiveData(); - activitiesLiveData.observe(this, new Observer>() { + MutableLiveData> activitiesLiveData = activityViewModel.getActivitiesLiveData(); + activitiesLiveData.observe(this, new Observer>() { @Override - public void onChanged(Map activities) { + public void onChanged(Map activities) { updateActivityView(activities); } }); @@ -116,6 +117,33 @@ openDialogButton.setOnClickListener(v -> showInputDialog()); } + @Override + protected void onStart() { + super.onStart(); + + if (activityViewModel != null) { + activityViewModel.startUpdating(1L); + } + } + + @Override + protected void onStop() { + super.onStop(); + + if (activityViewModel != null) { + activityViewModel.stopUpdating(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (activityViewModel != null) { + activityViewModel.stopUpdating(); + } + } + private void showInputDialog() { // カスタムビュー読み込み View dialogView = getLayoutInflater().inflate(R.layout.main_dialog, null); @@ -173,7 +201,7 @@ private void updateActivityView(Map activities) { TextView comment = this.findViewById(R.id.friend01_comment); - for (Activity ac: activities.values()) { + for (Activity ac : activities.values()) { comment.setText(ac.getText()); }