From 850459de9bb034b49ce43784eabd9f7fff177a37 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Sun, 31 Aug 2025 16:18:40 +0300 Subject: [PATCH] refactor: add use cases and decouple home viewmodel --- .../androidtutorials/java/di/AppModule.java | 12 +++++ .../home/GetAppPlayStoreUrlUseCase.java | 17 +++++++ .../domain/home/GetPlayStoreUrlUseCase.java | 17 +++++++ .../java/ui/screens/home/HomeFragment.java | 4 ++ .../java/ui/screens/home/HomeViewModel.java | 44 ++++++++++--------- .../ui/screens/home/HomeViewModelTest.java | 30 +++++++------ 6 files changed, 90 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetAppPlayStoreUrlUseCase.java create mode 100644 app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetPlayStoreUrlUseCase.java diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/di/AppModule.java b/app/src/main/java/com/d4rk/androidtutorials/java/di/AppModule.java index 1b1988f1..ec1664b1 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/di/AppModule.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/di/AppModule.java @@ -25,6 +25,8 @@ import com.d4rk.androidtutorials.java.domain.help.RequestReviewFlowUseCase; import com.d4rk.androidtutorials.java.domain.home.GetDailyTipUseCase; import com.d4rk.androidtutorials.java.domain.home.GetPromotedAppsUseCase; +import com.d4rk.androidtutorials.java.domain.home.GetPlayStoreUrlUseCase; +import com.d4rk.androidtutorials.java.domain.home.GetAppPlayStoreUrlUseCase; import com.d4rk.androidtutorials.java.domain.main.ApplyLanguageSettingsUseCase; import com.d4rk.androidtutorials.java.domain.main.ApplyThemeSettingsUseCase; import com.d4rk.androidtutorials.java.domain.main.BuildShortcutIntentUseCase; @@ -107,6 +109,16 @@ public GetPromotedAppsUseCase provideGetPromotedAppsUseCase(HomeRepository repos return new GetPromotedAppsUseCase(repository); } + @Provides + public GetPlayStoreUrlUseCase provideGetPlayStoreUrlUseCase(HomeRepository repository) { + return new GetPlayStoreUrlUseCase(repository); + } + + @Provides + public GetAppPlayStoreUrlUseCase provideGetAppPlayStoreUrlUseCase(HomeRepository repository) { + return new GetAppPlayStoreUrlUseCase(repository); + } + @Provides @Singleton public AboutRepository provideAboutRepository(Application application) { diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetAppPlayStoreUrlUseCase.java b/app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetAppPlayStoreUrlUseCase.java new file mode 100644 index 00000000..13d0ae31 --- /dev/null +++ b/app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetAppPlayStoreUrlUseCase.java @@ -0,0 +1,17 @@ +package com.d4rk.androidtutorials.java.domain.home; + +import com.d4rk.androidtutorials.java.data.repository.HomeRepository; + +/** Use case that builds a Play Store URL for a given package name. */ +public class GetAppPlayStoreUrlUseCase { + private final HomeRepository repository; + + public GetAppPlayStoreUrlUseCase(HomeRepository repository) { + this.repository = repository; + } + + /** Returns the Play Store URL for the specified package name. */ + public String invoke(String packageName) { + return repository.getAppPlayStoreUrl(packageName); + } +} diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetPlayStoreUrlUseCase.java b/app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetPlayStoreUrlUseCase.java new file mode 100644 index 00000000..c108099c --- /dev/null +++ b/app/src/main/java/com/d4rk/androidtutorials/java/domain/home/GetPlayStoreUrlUseCase.java @@ -0,0 +1,17 @@ +package com.d4rk.androidtutorials.java.domain.home; + +import com.d4rk.androidtutorials.java.data.repository.HomeRepository; + +/** Use case that provides the Play Store URL for this app. */ +public class GetPlayStoreUrlUseCase { + private final HomeRepository repository; + + public GetPlayStoreUrlUseCase(HomeRepository repository) { + this.repository = repository; + } + + /** Returns the Play Store URL for the application. */ + public String invoke() { + return repository.getPlayStoreUrl(); + } +} diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeFragment.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeFragment.java index f226bec8..b91fd5a3 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeFragment.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeFragment.java @@ -35,6 +35,10 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); homeViewModel = new ViewModelProvider(this).get(HomeViewModel.class); + homeViewModel.setAnnouncements( + getString(com.d4rk.androidtutorials.java.R.string.announcement_title), + getString(com.d4rk.androidtutorials.java.R.string.announcement_subtitle) + ); LayoutInflater inflater = LayoutInflater.from(requireContext()); homeViewModel.getUiState().observe(getViewLifecycleOwner(), state -> { binding.announcementTitle.setText(state.announcementTitle()); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModel.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModel.java index 4309a41f..f88009a2 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModel.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModel.java @@ -1,6 +1,5 @@ package com.d4rk.androidtutorials.java.ui.screens.home; -import android.app.Application; import android.content.Intent; import android.net.Uri; @@ -8,11 +7,11 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; -import com.d4rk.androidtutorials.java.R; import com.d4rk.androidtutorials.java.data.model.PromotedApp; -import com.d4rk.androidtutorials.java.data.repository.HomeRepository; import com.d4rk.androidtutorials.java.domain.home.GetDailyTipUseCase; import com.d4rk.androidtutorials.java.domain.home.GetPromotedAppsUseCase; +import com.d4rk.androidtutorials.java.domain.home.GetPlayStoreUrlUseCase; +import com.d4rk.androidtutorials.java.domain.home.GetAppPlayStoreUrlUseCase; import dagger.hilt.android.lifecycle.HiltViewModel; import javax.inject.Inject; @@ -24,26 +23,26 @@ @HiltViewModel public class HomeViewModel extends ViewModel { - private final Application application; - private final HomeRepository homeRepository; private final GetDailyTipUseCase getDailyTipUseCase; private final GetPromotedAppsUseCase getPromotedAppsUseCase; + private final GetPlayStoreUrlUseCase getPlayStoreUrlUseCase; + private final GetAppPlayStoreUrlUseCase getAppPlayStoreUrlUseCase; private final MutableLiveData uiState = new MutableLiveData<>(); @Inject - public HomeViewModel(Application application, - HomeRepository homeRepository, - GetDailyTipUseCase getDailyTipUseCase, - GetPromotedAppsUseCase getPromotedAppsUseCase) { - this.application = application; - this.homeRepository = homeRepository; + public HomeViewModel(GetDailyTipUseCase getDailyTipUseCase, + GetPromotedAppsUseCase getPromotedAppsUseCase, + GetPlayStoreUrlUseCase getPlayStoreUrlUseCase, + GetAppPlayStoreUrlUseCase getAppPlayStoreUrlUseCase) { this.getDailyTipUseCase = getDailyTipUseCase; this.getPromotedAppsUseCase = getPromotedAppsUseCase; + this.getPlayStoreUrlUseCase = getPlayStoreUrlUseCase; + this.getAppPlayStoreUrlUseCase = getAppPlayStoreUrlUseCase; HomeUiState initialState = new HomeUiState( - application.getString(R.string.announcement_title), - application.getString(R.string.announcement_subtitle), + "", + "", getDailyTipUseCase.invoke(), new ArrayList<>() ); @@ -75,6 +74,16 @@ public HomeViewModel(Application application, }); } + public void setAnnouncements(String title, String subtitle) { + HomeUiState current = uiState.getValue(); + if (current == null) { + current = new HomeUiState(title, subtitle, getDailyTipUseCase.invoke(), new ArrayList<>()); + } else { + current = new HomeUiState(title, subtitle, current.dailyTip(), current.promotedApps()); + } + uiState.setValue(current); + } + /** * Exposes the UI state for the Home screen. */ @@ -87,22 +96,17 @@ public LiveData getUiState() { * The HomeFragment can startActivity(...) on it. */ public Intent getOpenPlayStoreIntent() { - return buildPlayStoreIntent(homeRepository.getPlayStoreUrl()); + return buildPlayStoreIntent(getPlayStoreUrlUseCase.invoke()); } /** * Builds an intent to open the Google Play listing for the provided package. */ public Intent getPromotedAppIntent(String packageName) { - return buildPlayStoreIntent(homeRepository.getAppPlayStoreUrl(packageName)); + return buildPlayStoreIntent(getAppPlayStoreUrlUseCase.invoke(packageName)); } private Intent buildPlayStoreIntent(String url) { - Intent playStoreIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - playStoreIntent.setPackage("com.android.vending"); - if (playStoreIntent.resolveActivity(application.getPackageManager()) != null) { - return playStoreIntent; - } return new Intent(Intent.ACTION_VIEW, Uri.parse(url)); } } \ No newline at end of file diff --git a/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java b/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java index 7be4482b..2da8534c 100644 --- a/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java +++ b/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java @@ -1,14 +1,13 @@ package com.d4rk.androidtutorials.java.ui.screens.home; -import android.app.Application; - import androidx.arch.core.executor.testing.InstantTaskExecutorRule; -import com.d4rk.androidtutorials.java.R; import com.d4rk.androidtutorials.java.data.model.PromotedApp; import com.d4rk.androidtutorials.java.data.repository.HomeRepository; import com.d4rk.androidtutorials.java.domain.home.GetDailyTipUseCase; import com.d4rk.androidtutorials.java.domain.home.GetPromotedAppsUseCase; +import com.d4rk.androidtutorials.java.domain.home.GetPlayStoreUrlUseCase; +import com.d4rk.androidtutorials.java.domain.home.GetAppPlayStoreUrlUseCase; import org.junit.Rule; import org.junit.Test; @@ -17,7 +16,6 @@ import static org.junit.Assert.*; -import org.mockito.Mockito; public class HomeViewModelTest { @@ -44,11 +42,13 @@ static class FakeHomeRepository implements HomeRepository { public void uiStateUpdatesWithData() { List promoted = List.of(new PromotedApp("App", "pkg", "icon")); FakeHomeRepository repo = new FakeHomeRepository("tip", promoted); - Application app = Mockito.mock(Application.class); - Mockito.when(app.getString(R.string.announcement_title)).thenReturn("Title"); - Mockito.when(app.getString(R.string.announcement_subtitle)).thenReturn("Subtitle"); - HomeViewModel viewModel = new HomeViewModel(app, repo, - new GetDailyTipUseCase(repo), new GetPromotedAppsUseCase(repo)); + HomeViewModel viewModel = new HomeViewModel( + new GetDailyTipUseCase(repo), + new GetPromotedAppsUseCase(repo), + new GetPlayStoreUrlUseCase(repo), + new GetAppPlayStoreUrlUseCase(repo) + ); + viewModel.setAnnouncements("Title", "Subtitle"); HomeUiState state = viewModel.getUiState().getValue(); assertNotNull(state); @@ -61,11 +61,13 @@ public void uiStateUpdatesWithData() { @Test public void uiStateHandlesEmptyPromotedApps() { FakeHomeRepository repo = new FakeHomeRepository("tip", List.of()); - Application app = Mockito.mock(Application.class); - Mockito.when(app.getString(R.string.announcement_title)).thenReturn("Title"); - Mockito.when(app.getString(R.string.announcement_subtitle)).thenReturn("Subtitle"); - HomeViewModel viewModel = new HomeViewModel(app, repo, - new GetDailyTipUseCase(repo), new GetPromotedAppsUseCase(repo)); + HomeViewModel viewModel = new HomeViewModel( + new GetDailyTipUseCase(repo), + new GetPromotedAppsUseCase(repo), + new GetPlayStoreUrlUseCase(repo), + new GetAppPlayStoreUrlUseCase(repo) + ); + viewModel.setAnnouncements("Title", "Subtitle"); HomeUiState state = viewModel.getUiState().getValue(); assertNotNull(state);