A simple and easy to use flash card app to help you study.
- Easily add deck and cards
- Add notification timer to periodically asking you question
- Support dark mode and light mode
- Easily export & share your decks to your friends
- Record voices and attach images for the cards
- Create shortcut to show random card from deck for casual study (Android 8 and above)
- Flash bot to smartly suggest list of card to test you
This project is a multi-module Android application.
graph TD
App[:app] --> Base[:base]
App --> Bot[:bot]
App --> Timer[:timer-notification]
Bot --> Base
Timer --> Base
:app: The main application module that contains the UI and presentation layer.:base: A library module that contains base classes and utilities shared across other modules.:bot: A library module that contains the logic for the "Flash bot" feature.:timer-notification: A library module for handling timer-based notifications.
This project is intended for demo app for a-navigator and a-provider library usage. The app still works as production even though it is demo app.
The app follows a modern Android architecture, utilizing a combination of established libraries and custom frameworks to create a modular and maintainable codebase.
-
Dependency Injection: The app uses a custom service locator pattern with the
a-providerlibrary. A globalProvideris initialized in theMainApplicationclass, which is then used to provide dependencies throughout the app. This creates a centralized and easy-to-manage dependency graph. -
Navigation: Navigation between screens is handled by the
a-navigatorlibrary. This library provides a flexible and powerful way to manage navigation, including support for different screen types and transitions. -
Reactive Programming: The app makes extensive use of
RxJavafor handling asynchronous operations and UI events. This allows for a more concise and readable code, especially when dealing with complex asynchronous workflows. -
Modular Design: The app is divided into several modules, each with a specific responsibility. This promotes a clean separation of concerns and makes the codebase easier to understand and maintain.
-
Error Handling: A global exception handler is set up in
MainApplicationto log crashes and other unexpected errors. This helps to ensure that the app is as stable as possible. -
Background Jobs: Background tasks are handled by
WorkManager, with a custom configuration provided byMainApplication. This allows for efficient and reliable execution of background tasks, such as syncing data or sending notifications.
The presentation layer is built on a custom component-based architecture centered around the StatefulView class. This pattern deviates from traditional MVP or MVVM in favor of a more self-contained and reactive approach.
Here’s a breakdown of the workflow:
-
View and Logic Combined:
StatefulViewclasses (e.g.,HomePage,SettingsPage) are responsible for both creating the AndroidViewand handling the presentation logic. This makes eachStatefulViewa self-contained UI component. -
Lifecycle: The
a-navigatorlibrary manages the lifecycle ofStatefulViews. When you navigate to a new screen, the navigator creates the correspondingStatefulViewinstance. Thedisposemethod is then called when the view is no longer needed, which is crucial for unsubscribing from RxJava streams and preventing memory leaks. -
Dependency Injection: Dependencies are injected in two ways:
- The navigator injects navigation-related components (like
INavigatorandAppBarSV) using the@NavInjectannotation. - Other dependencies (like data sources, commands, and notifiers) are provided by the
a-providerservice locator via theprovideComponentmethod.
- The navigator injects navigation-related components (like
-
UI Creation: The
createViewmethod is where the UI is constructed. It inflates an XML layout, findsViews by their IDs, and sets up event listeners. It also subscribes to RxJava streams to react to state changes. -
State Management and Reactivity: The UI state is managed using RxJava’s
BehaviorSubject. The UI elements subscribe to these subjects, so whenever the state changes (e.g., a test starts or stops), the UI updates automatically and reactively. -
User Interaction: User actions, handled in methods like
onClick, trigger business logic by calling command classes (e.g.,mNewCardCmd,mTestStateModifier.startTest). These commands perform operations and update the state, which in turn updates the UI through the reactive streams.
sequenceDiagram
participant Nav as Navigator
participant SV as StatefulView
participant UI as View (XML)
participant Rx as RxJava Subject
Nav->>SV: Instantiate (via Provider)
Nav->>SV: createView(activity, container)
SV->>UI: Inflate layout
SV->>UI: Find views & set listeners
SV->>Rx: Subscribe to state changes
Rx-->>SV: Emit current state
SV->>UI: Update UI
Note over Nav, SV: Navigation or Back Press
Nav->>SV: dispose()
SV->>Rx: Unsubscribe (prevent leaks)
The application's workflow is designed to be modular, scalable, and reactive. It follows a clear separation of concerns, with distinct layers for the UI, business logic, and data.
The data layer is responsible for all data-related operations. It is built on top of the following components:
- Room Persistence Library: The app uses Room for local data persistence. It has two databases:
AppDatabase: The main database for the app, managing entities likeDeck,Card,Test,AndroidNotification, andNotificationTimer.BotDatabase: A separate database for the "Flash bot" feature, managing entities likeCardLogandSuggestedCard.
- DAOs (Data Access Objects): Each entity has a corresponding DAO that defines the methods for accessing and manipulating the data in the database.
The business logic is encapsulated in command classes, which follow the command pattern. These commands are responsible for executing specific business operations, such as creating a new deck or updating a card.
Some of the key command classes include:
NewDeckCmd: Creates a new deck.UpdateCardCmd: Updates an existing card.DeleteDeckCmd: Deletes a deck.ExportImportCmd: Handles the export and import of decks.
These commands are provided by the CommandProviderModule and are injected into the StatefulViews where they are needed.
The app's data flow is designed to be unidirectional and reactive, ensuring that the UI is always in sync with the underlying data. Here's a step-by-step overview of the data flow:
sequenceDiagram
participant User
participant SV as StatefulView
participant Cmd as Command
participant DAO as DAO/Room
participant Notifier
participant Rx as RxJava
User->>SV: Interaction (Click)
SV->>Cmd: Execute Command
Cmd->>DAO: Update Data
DAO-->>Cmd: Success
Cmd->>Notifier: Broadcast Change
Notifier->>Rx: Emit Event
Rx-->>SV: OnNext(Event)
SV->>UI: Update View
- User Interaction: The user interacts with a
StatefulView(e.g., clicks a button). - Command Execution: The
StatefulViewinvokes the appropriate command to handle the user's action. - Data Manipulation: The command interacts with the data layer (via the DAOs) to create, read, update, or delete data.
- State Notification: After the data is updated, the command uses a notifier (e.g.,
DeckChangeNotifier) to broadcast that the data has changed. - UI Update: The
StatefulViews subscribe to these notifiers and update their UI in response to the change notifications. This is done reactively usingRxJava, ensuring that the UI always reflects the current state of the data.
To ensure that the UI remains responsive, all database and business logic operations are performed on background threads. This is achieved through a combination of RxJava schedulers and a dedicated ExecutorService, which is provided by the BaseProviderModule.
The project includes both unit and instrumentation tests, although the coverage could be improved. Here's a summary of the testing strategy:
-
Unit Tests: The project has a
unitTestartifact, but it currently only contains a boilerplate example. This is an area for improvement, as the business logic in the command classes and other components could be unit-tested with mocked dependencies. -
Instrumentation Tests: The project has an
androidTestartifact with at least one meaningful test,ExportImportCmdTest.java. This test demonstrates a good approach to testing database interactions and file operations, using a separateProviderand an in-memory database to ensure that tests are hermetic and isolated. -
Areas for Improvement: In addition to adding more unit tests, the project would benefit from UI tests using a framework like Espresso. This would allow for the verification of the application's user interface and user flows, ensuring that the app behaves as expected from the user's perspective.
The project uses a combination of GitHub Actions and Fastlane to automate the build, test, and release process.
The project has three GitHub Actions workflows:
gradlew-build.yml: Builds the project with Gradle on every push and pull request to themasterbranch.android-release.yml: Creates a GitHub release and attaches the debug and release APKs when a new tag starting with "v" is pushed.android-emulator-test.yml: Runs Android instrumentation tests on an emulator on every push and pull request to themasterbranch.
The project uses Fastlane to manage the app's metadata for the Google Play Store. This includes the app's title, description, screenshots, and changelogs. The metadata is stored in the fastlane/metadata directory and is organized by language.
- Clone the repository:
git clone https://github.com/rh-id/a-flash-deck.git - Open the project in Android Studio.
- Build the project using Gradle:
./gradlew assembleDebug
The app uses a-navigator framework as navigator and StatefulView as base structure, combined with a-provider library for service locator, and finally RxAndroid to handle UI use cases.
Consider donation to support this project
| https://teer.id/rh-id |