diff --git a/docs/core/core-module.md b/docs/core/core-module.md index 6ab8dc1f..aa1db163 100644 --- a/docs/core/core-module.md +++ b/docs/core/core-module.md @@ -1,52 +1,53 @@ # Core Module -The **core** package provides foundational building blocks shared across AppToolkit features. It offers base domain models, UI abstractions, utility helpers and dependency injection qualifiers used throughout the library. +The **core** package provides building blocks shared across the app. It offers domain models, ViewModel classes, utility helpers and dependency injection qualifiers used throughout the project. ## Packages ### domain -Defines reusable result wrappers and UI state models, plus base use case interfaces for repositories and operations. +Houses use case classes and other business logic that operate on repositories. ### ui -Hosts composable components and base classes like `ScreenViewModel` and `DefaultSnackbarHost` that standardize screen state handling and Snackbar presentation. +Contains Activities, Fragments and ViewModels such as `MainViewModel`. ### utils -Includes helpers, extensions, constants and `DispatcherProvider` to access standard `CoroutineDispatcher` instances. +Provides helpers like `OpenSourceLicensesUtils`, `ReviewHelper` and `EdgeToEdgeDelegate`. ### di -Contains qualifiers such as `GithubToken` to assist dependency injection frameworks. +Contains Hilt modules and qualifiers for dependency injection. ## Usage examples -### ScreenViewModel -```kotlin -class ExampleViewModel : ScreenViewModel( - initialState = UiStateScreen(data = UiScreen()) -) { - // handle events +### ViewModel +```java +public class MainViewModel extends ViewModel { + private final ShouldShowStartupScreenUseCase shouldShowStartupScreenUseCase; + + public MainViewModel(ShouldShowStartupScreenUseCase shouldShowStartupScreenUseCase) { + this.shouldShowStartupScreenUseCase = shouldShowStartupScreenUseCase; + } + + public boolean shouldShowStartupScreen() { + return shouldShowStartupScreenUseCase.invoke(); + } } ``` -### DefaultSnackbarHost -```kotlin -val snackbarHostState = remember { SnackbarHostState() } - -Scaffold( - snackbarHost = { DefaultSnackbarHost(snackbarState = snackbarHostState) } -) { /* screen content */ } +### Snackbar helper +```java +Snackbar.make(view, "Message", Snackbar.LENGTH_SHORT).show(); ``` -### DispatcherProvider -```kotlin -class ExampleRepository(private val dispatchers: DispatcherProvider) { - suspend fun load() = withContext(dispatchers.io) { - /* blocking work */ - } -} +### ReviewHelper +```java +ReviewHelper.launchInAppReviewIfEligible(activity, sessionCount, hasPromptedBefore, () -> { + // callback when review flow finishes +}); ``` ## See also -- [[Library]] – overview of all modules and features. -- [[Issue-Reporter-Module]] – demonstrates use of `ScreenViewModel` and networking helpers. -- [[Support-Module]] – integrates `DispatcherProvider` for billing and donation flows. +- [[Architecture]] – overview of app layers. +- [[Data Layer]] – repository and data source details. +- [[UI Components]] – common reusable views. + diff --git a/docs/core/data-layer.md b/docs/core/data-layer.md index 14fff679..e62f6aa3 100644 --- a/docs/core/data-layer.md +++ b/docs/core/data-layer.md @@ -1,42 +1,80 @@ # Data Layer -This page outlines the data-related building blocks provided by AppToolkit. +This page outlines how the app manages and persists data. -## HTTP client +## Repositories -`apptoolkit/data/client` exposes **KtorClient**, a factory for a preconfigured Ktor `HttpClient`. It installs JSON content negotiation, request timeouts, default headers and optional logging so callers only need to supply their own endpoints. +Repositories expose data to the rest of the app and hide the underlying storage. -### Setup - -```kotlin -val client = KtorClient().createClient(enableLogging = true) +```java +public interface MainRepository { + boolean shouldShowStartupScreen(); + void markStartupScreenShown(); +} ``` -## DataStore - -The `apptoolkit/data/datastore` package wraps Android DataStore in a singleton `CommonDataStore`. It centralizes preferences such as startup flags, theme options and user consents, exposing them as Kotlin `Flow`s with suspend functions to persist updates. +`DefaultMainRepository` implements these methods using `SharedPreferences`: -### Usage +```java +public class DefaultMainRepository implements MainRepository { + private final Context context; -```kotlin -val dataStore = CommonDataStore.getInstance(context) + public DefaultMainRepository(Context context) { + this.context = context.getApplicationContext(); + } -// Observe a value -val adsEnabled = dataStore.ads(default = true) + @Override + public boolean shouldShowStartupScreen() { + SharedPreferences startup = context.getSharedPreferences("startup", Context.MODE_PRIVATE); + return startup.getBoolean("value", true); + } -// Save a value -scope.launch { dataStore.saveThemeMode("dark") } + @Override + public void markStartupScreenShown() { + SharedPreferences startup = context.getSharedPreferences("startup", Context.MODE_PRIVATE); + startup.edit().putBoolean("value", false).apply(); + } +} ``` -## Ads +## Data sources + +Remote and local sources supply the repositories with data. For example, `DefaultHomeRemoteDataSource` uses Volley to fetch promoted apps: + +```java +public class DefaultHomeRemoteDataSource implements HomeRemoteDataSource { + private final RequestQueue requestQueue; + private final String apiUrl; + + public DefaultHomeRemoteDataSource(RequestQueue requestQueue, String apiUrl) { + this.requestQueue = requestQueue; + this.apiUrl = apiUrl; + } + + @Override + public void fetchPromotedApps(PromotedAppsCallback callback) { + JsonObjectRequest request = new JsonObjectRequest( + Request.Method.GET, + apiUrl, + null, + response -> { /* parse and callback */ }, + error -> { /* handle error */ } + ); + requestQueue.add(request); + } +} +``` -Ads are configured through preferences in `CommonDataStore` via the `ads` flag and related consent entries. The `core/ads` package provides `AdsCoreManager`, which checks those preferences before initializing Google Mobile Ads and manages app-open ad loading and display. +## Models -Use `AdsCoreManager` when the application should show an app-open ad on start or resume: +Model classes like `PromotedApp` encapsulate the data returned by the layer: -```kotlin -val adsManager = AdsCoreManager(context, buildInfoProvider) -scope.launch { adsManager.initializeAds("ca-app-pub-xxxxxxxxxxxxxxxx/xxxxxxxxxx") } -adsManager.showAdIfAvailable(activity, scope) +```java +public record PromotedApp(String name, String packageName, String iconUrl) {} ``` +## See also + +- [[Architecture]] – overview of app layers. +- [[Core Module]] – shared utilities and components. + diff --git a/docs/core/solid-principles-android.md b/docs/core/solid-principles-android.md index e0ad56c7..489a7a3e 100644 --- a/docs/core/solid-principles-android.md +++ b/docs/core/solid-principles-android.md @@ -1,4 +1,4 @@ -# SOLID Principles in Android Development with Kotlin +# SOLID Principles in Android Development with Java ## Introduction SOLID is an acronym for five design principles that help create maintainable and scalable codebases. Applying these ideas in Android projects keeps features isolated, encourages extension without breaking existing behavior and makes code easier to test. @@ -7,33 +7,41 @@ SOLID is an acronym for five design principles that help create maintainable and Each class should have one reason to change. Splitting responsibilities into distinct components improves clarity and testability. ### Violation -```kotlin -class ItemManager(private val context: Context) { - private val items = mutableListOf() +```java +class ItemManager { + private final Context context; + private final List items = new ArrayList<>(); - fun retrieveAndDisplayItems() { - val items = retrieveItemsFromServer() - val recyclerView = RecyclerView(context) - val adapter = ItemListAdapter(items) - recyclerView.adapter = adapter + ItemManager(Context context) { + this.context = context; } - fun retrieveItemsFromServer(): List = emptyList() + void retrieveAndDisplayItems() { + List items = retrieveItemsFromServer(); + RecyclerView recyclerView = new RecyclerView(context); + ItemListAdapter adapter = new ItemListAdapter(items); + recyclerView.setAdapter(adapter); + } + + List retrieveItemsFromServer() { + return Collections.emptyList(); + } - fun storeItemsLocally(items: List) { + void storeItemsLocally(List items) { // Save items to the local database } } ``` ### Adherence -```kotlin +```java class ItemRepository { - fun fetchItems(): List { + List fetchItems() { // Fetch items from a server or database + return Collections.emptyList(); } - fun saveItems(items: List) { + void saveItems(List items) { // Persist items to a database } } @@ -43,40 +51,48 @@ class ItemRepository { Software entities should be open for extension and closed for modification. Favor abstraction so new behavior can be added without altering existing code. ### Violation -```kotlin +```java class ItemService { - fun calculateTotalPrice(cart: List, discount: Double): Double { - var totalPrice = 0.0 - for (item in cart) { - totalPrice += item.price + double calculateTotalPrice(List cart, double discount) { + double totalPrice = 0.0; + for (Item item : cart) { + totalPrice += item.getPrice(); } - totalPrice *= (1.0 - discount) - return totalPrice + totalPrice *= (1.0 - discount); + return totalPrice; } } ``` ### Adherence -```kotlin +```java interface PriceCalculator { - fun calculateTotalPrice(cart: List): Double + double calculateTotalPrice(List cart); } -class BasicPriceCalculator : PriceCalculator { - override fun calculateTotalPrice(cart: List): Double { - var totalPrice = 0.0 - for (product in cart) { - totalPrice += product.price +class BasicPriceCalculator implements PriceCalculator { + @Override + public double calculateTotalPrice(List cart) { + double totalPrice = 0.0; + for (Product product : cart) { + totalPrice += product.getPrice(); } - return totalPrice + return totalPrice; } } -class DiscountedPriceCalculator(private val discount: Double) : PriceCalculator { - override fun calculateTotalPrice(cart: List): Double { - val basic = BasicPriceCalculator() - val total = basic.calculateTotalPrice(cart) - return total * (1.0 - discount) +class DiscountedPriceCalculator implements PriceCalculator { + private final double discount; + + DiscountedPriceCalculator(double discount) { + this.discount = discount; + } + + @Override + public double calculateTotalPrice(List cart) { + PriceCalculator basic = new BasicPriceCalculator(); + double total = basic.calculateTotalPrice(cart); + return total * (1.0 - discount); } } ``` @@ -85,30 +101,32 @@ class DiscountedPriceCalculator(private val discount: Double) : PriceCalculator Subclasses must be replaceable for their base types without altering the correctness of the program. ### Violation -```kotlin -open class Bird { - open fun fly() { +```java +class Bird { + void fly() { // Default flying behavior } } -class Dog : Bird() { - override fun fly() { - throw UnsupportedOperationException("Dogs can't fly") +class Dog extends Bird { + @Override + void fly() { + throw new UnsupportedOperationException("Dogs can't fly"); } } ``` ### Adherence -```kotlin -open class Bird { - open fun move() { +```java +class Bird { + void move() { // Default movement behavior } } -class Ostrich : Bird() { - override fun move() { +class Ostrich extends Bird { + @Override + void move() { // Ostriches move by running } } @@ -118,39 +136,43 @@ class Ostrich : Bird() { Clients should not be forced to implement methods they do not use. Split broad interfaces into focused ones. ### Violation -```kotlin +```java interface Worker { - fun work() - fun eat() + void work(); + void eat(); } -class SuperWorker : Worker { - override fun work() { +class SuperWorker implements Worker { + @Override + public void work() { // Working behavior } - override fun eat() { + @Override + public void eat() { // Eating behavior } } ``` ### Adherence -```kotlin +```java interface Workable { - fun work() + void work(); } interface Eatable { - fun eat() + void eat(); } -class SuperWorker : Workable, Eatable { - override fun work() { +class SuperWorker implements Workable, Eatable { + @Override + public void work() { // Working behavior } - override fun eat() { + @Override + public void eat() { // Eating behavior } } @@ -160,40 +182,48 @@ class SuperWorker : Workable, Eatable { High-level modules should depend on abstractions rather than concrete implementations. ### Violation -```kotlin +```java class LightBulb { - fun turnOn() { + void turnOn() { // Turn on the light bulb } } class Switch { - private val bulb = LightBulb() + private final LightBulb bulb = new LightBulb(); - fun control() { - bulb.turnOn() + void control() { + bulb.turnOn(); } } ``` ### Adherence -```kotlin +```java interface Switchable { - fun turnOn() + void turnOn(); } -class LightBulb : Switchable { - override fun turnOn() { +class LightBulb implements Switchable { + @Override + public void turnOn() { // Turn on the light bulb } } -class Switch(private val device: Switchable) { - fun control() { - device.turnOn() +class Switch { + private final Switchable device; + + Switch(Switchable device) { + this.device = device; + } + + void control() { + device.turnOn(); } } ``` ## Conclusion Applying the SOLID principles in Android projects encourages separation of concerns, extensibility and decoupling. These guidelines lead to code that is easier to understand, test and evolve over time. + diff --git a/docs/core/ui-components.md b/docs/core/ui-components.md index 89ba9c75..58e9f626 100644 --- a/docs/core/ui-components.md +++ b/docs/core/ui-components.md @@ -1,92 +1,94 @@ # UI Components -This page groups common Jetpack Compose components available in AppToolkit. +This page groups common Android View components used in AppToolkit. ## Buttons -Use buttons to trigger actions. Compose offers `Button`, `OutlinedButton`, and `IconButton`. +Use buttons to trigger actions. -AppToolkit wraps `IconButton`, `FilledIconButton`, `FilledTonalIconButton`, and `OutlinedIconButton` with -Material 3's expressive shape morphing via `IconButtonDefaults.shapes()`, providing round-to-square -transitions in response to interaction states. +**XML** -```kotlin -Button(onClick = { /* handle action */ }) { - Text("Submit") -} +```xml +