An Android pet project for browsing, searching, filtering, and saving characters from the Rick and Morty API.
This repository started as an early MVVM learning project and has since been refreshed in three focused areas:
- A production-shaped Paging 3 data flow with one filter state, one paging stream, lifecycle-aware collection, explicit load states, and tested page keys.
- Consistent edge-to-edge layouts with explicit status bar, navigation bar, display cutout, and keyboard inset handling.
- A cohesive Material 3 redesign with a portal-inspired day/night palette, aligned character cards, clearer screen hierarchy, and polished empty and loading states.
It remains intentionally small and View-based, making it useful as a reference when migrating an older Android application without also introducing Compose or a broad build-system upgrade.
| Welcome screen | Character browser | Character details |
|---|---|---|
![]() |
![]() |
![]() |
- Browse characters with paginated network loading.
- Search by character name.
- Filter results by male or female characters.
- Display initial loading, empty, refresh-error, append-loading, and append-error states.
- Retry failed initial and incremental loads.
- View character details.
- Save characters locally with Room.
- View saved characters and remove one with swipe-to-delete and undo.
- Delete all saved characters after confirmation.
- Store and display a local profile name with
SharedPreferences. - Open Rick and Morty web and YouTube pages or share the app through Android intents.
- Render consistently edge to edge across the intro, login, list, details, drawer, and saved-character screens.
The project uses a small MVVM-style structure with a shared repository for network and database operations.
flowchart LR
UI["Activities and adapters"] --> VM["ViewModels"]
VM --> REPO["Repository"]
REPO --> PAGER["Pager and PagingSource"]
PAGER --> API["Rick and Morty REST API"]
REPO --> DAO["Room DAO"]
DAO --> DB["Room database"]
app/src/main/java/com/example/rickandmortymvvm/
├── core/ Shared utilities and UI helpers
├── data/
│ ├── api/ Retrofit service and PagingSource
│ └── db/ Room database and DAO
├── di/ Hilt providers and Application class
├── domain/
│ ├── model/ API and database models
│ └── repo/ Repository and Pager construction
└── presentation/
├── view/ Activities and RecyclerView adapters
└── viewmodel/ Screen state and data-flow ownership
The character list is driven by a single CharacterFilters value containing the current search query and gender. That value is stored in SavedStateHandle, so it survives Activity recreation and process restoration.
CharacterFilters StateFlow
↓
flatMapLatest — cancel the obsolete Pager when filters change
↓
Repository.searchCharacters()
↓
Pager.flow
↓
cachedIn(viewModelScope)
↓
repeatOnLifecycle(STARTED)
↓
PagingDataAdapter.submitData()
Important details demonstrated here:
flatMapLatestguarantees that only the newest search/filter combination owns the list.cachedIn(viewModelScope)is applied after switching paging streams.- The Activity has one PagingData collector instead of separate observers for each filter.
PagingDataAdapterandDiffUtilown list updates;notifyDataSetChanged()is not used.PagingSourceuses the API'sinfo.nextandinfo.prevmetadata rather than guessing from item count.- Refresh errors are rendered by the screen, while prepend and append errors are rendered by
LoadStateAdapterinstances. - Opening Details or Saved does not destroy the list Activity, allowing the ViewModel cache and scroll state to remain available.
The Rick and Morty API returns HTTP 404 when a character filter has no matches. This project intentionally converts that API-specific response into an empty terminal page. Do not apply that rule generically in a production API client unless the service contract defines 404 as an empty search result.
Every Activity calls enableEdgeToEdge() and then applies insets to the view that owns each safe area:
- App-bar containers receive the status-bar and display-cutout top inset.
- RecyclerViews and detail content receive the navigation-bar bottom inset.
- Full-screen intro and login layouts receive all system-bar edges.
- The login screen also responds to IME insets so the keyboard does not cover its content.
- The navigation drawer handles its own safe-area padding.
The shared inset helper records the original XML padding before adding system insets. That prevents padding from accumulating when Android redispatches insets after rotation, keyboard changes, or navigation-mode changes.
| Area | Implementation |
|---|---|
| Language | Kotlin |
| UI | XML layouts, View Binding, Material 3 |
| Architecture | MVVM-style Activities, ViewModels, repository |
| Pagination | Paging 3, PagingSource, PagingDataAdapter, load states |
| Async state | Coroutines, Flow, StateFlow, LiveData for saved characters |
| Networking | Retrofit, OkHttp, Gson |
| Persistence | Room, SharedPreferences |
| Dependency injection | Hilt |
| Images | Glide |
| Minimum Android version | API 21 |
| Compile / target SDK | 36 / 35 |
| Java toolchain | Java 17 |
- Android Studio with Android SDK 36 installed.
- JDK 17.
- An emulator or device running API 21 or newer.
The public character API does not require an API key.
git clone git@github.com:GetRighhttt/RickAndMortyAndroidMVVM.git
cd RickAndMortyAndroidMVVM
./gradlew assembleDebugAlternatively, open the repository in Android Studio, allow Gradle sync to finish, and run the app configuration.
Run the local unit tests with:
./gradlew testDebugUnitTestThe focused PagingSource tests cover:
- Previous and next page keys.
- End-of-pagination behavior.
- Empty filter results.
- HTTP errors.
- Refresh-key recovery around the visible item.
To build and test together:
./gradlew testDebugUnitTest assembleDebugThis repository demonstrates the Paging and inset patterns, but it is not a production template as-is. Before adopting the full project setup, consider the following:
- Use a
RemoteMediatorand a database-backed source of truth if paged content must work offline. - Replace the local profile-name screen with real authentication if identity is required.
- Replace
fallbackToDestructiveMigration()with explicit Room migrations. - Disable BODY-level HTTP logging in release builds.
- Configure real release signing; this project currently uses debug signing for the release build type.
- Add instrumentation/UI tests for loading, retry, rotation, keyboard, cutout, gesture-navigation, and three-button-navigation behavior.
- Review and upgrade the Gradle, Kotlin, KSP, AndroidX, networking, and DI versions as a separate migration.
- Remove unused legacy dependencies and classes after confirming they are not needed by downstream experiments.
The current build still reports legacy plugin and deprecation warnings. Those are deliberately outside the Paging 3 and edge-to-edge work documented here.
- Character browsing, searching, saving, and deletion
- Local profile name and saved-character persistence
- YouTube, website, and Android share intents
The videos show earlier versions of the project, so small UI and behavior differences may exist.
For changes intended as reference material:
- Keep the sample focused and explain non-obvious lifecycle or API-contract decisions.
- Add or update tests for PagingSource keys, errors, and terminal-page behavior.
- Run
./gradlew testDebugUnitTest assembleDebug. - Open a pull request describing both the behavior change and the production lesson it demonstrates.
Character data and images are provided by the Rick and Morty API. This is an unofficial learning project and is not affiliated with the API maintainers, Adult Swim, or the creators of Rick and Morty.
Questions and comments: stefanbusiness95@gmail.com


