Mobile Development Kotlin · Android Winter 2025 CIS 436 3 Projects

Android
Development

Three projects across one semester

Three Android apps built across a mobile development course, each introducing a new layer of Android architecture — from fragment communication patterns to MVVM with LiveData to a full Room database with Navigation component and reactive UI.

Project 01
Tic Tac Toe

A two-player Tic Tac Toe game demonstrating Android fragment communication patterns and separation of game logic from UI.

Kotlin Fragment Communication Singleton Pattern View Binding
01
Fragment Communication
Listener interfaces via MainActivity

The game is split across two fragments — BoardFragment renders the 3×3 grid and handles move input, while StatusFragment displays the current player and a reset button. The two fragments never reference each other directly.

Instead, each fragment defines a listener interface for the actions it needs to trigger. MainActivity implements both interfaces and acts as the coordinator — routing calls between fragments without them needing to know the other exists. This is the classic Android fragment communication pattern — modern apps often use a shared ViewModel instead, but the principle of fragments not referencing each other directly remains the same.

Configuration changes
restoreBoard() repopulates the grid from the singleton's state on every onCreateView — so rotating the screen mid-game preserves the board exactly as it was.
Tic Tac Toe — board and status fragments
Board and status fragments — game mid-play
Kotlin MainActivity.kt — routing between fragments
// MainActivity implements both fragment interfaces —
// fragments define what they need, activity routes it
class MainActivity : AppCompatActivity(),
    BoardFragment.BoardFragmentListener,
    StatusFragment.StatusFragmentListener
{
    override fun UpdateStatus(status: String) {
        // Board tells Activity → Activity tells Status
        val statusFragment = binding.statusFragment
            .getFragment<StatusFragment>()
        statusFragment.UpdateStatus(status)
    }

    override fun resetBoard() {
        // Status tells Activity → Activity tells Board
        val boardFragment = binding.boardfragment
            .getFragment<BoardFragment>()
        boardFragment.resetBoard()
    }
}

Game logic lives entirely in a TicTacToe singleton — move validation, win detection, draw detection, player switching. The fragments only call into it and render what it returns. No game state lives in the UI layer.

Tic Tac Toe landscape
Landscape layout

Project 02
Cat Breed Browser

An app that fetches cat breed data from The Cat API and displays breed details with images — built around MVVM architecture with LiveData and Volley.

MVVM LiveData Volley Glide The Cat API
01
MVVM Architecture
ViewModel, LiveData, and encapsulation

ViewModelCatViewModel owns all data and survives configuration changes like screen rotation. It fetches breeds from The Cat API via Volley on initialization and exposes results through LiveData. Both fragments share the same ViewModel instance via ViewModelProvider(requireActivity()).

MutableLiveData vs LiveData — the ViewModel holds MutableLiveData privately so only it can write new values. Fragments get a read-only LiveData reference — they can observe changes but can't set values directly, keeping data flow strictly one-directional.

Glide handles image loading from the API's image URLs — placeholder while loading, error image on failure — without the fragment managing any of that complexity manually.

Data flow — breed selection
CatViewModel
Fetches breeds, holds MutableLiveData
↓ exposes read-only LiveData
BreedSelectorFragment
Spinner populated from breedsList LiveData
↓ user selects → viewModel.selectBreed()
CatViewModel
Updates _selectedBreed MutableLiveData
↓ selectedBreed LiveData emits
CatDetailsFragment
Observer fires → updateUI() with breed data + Glide image
Cat Breed Browser — breed selected with details
Breed selected — details and image loaded from The Cat API
Spinner display
ArrayAdapter calls toString() on each item to render it in the spinner. CatBreed overrides toString() to return just the breed name — without this the spinner would show raw object references.

Project 03
Todo List

A full-featured task manager with local persistence, multi-screen navigation, and a reactive UI that animates smoothly as tasks are completed and reordered.

Room Navigation Component ListAdapter + DiffCallback Safe Args Repository Pattern TypeConverters
01
Reactive Data Pipeline
Room → LiveData → RecyclerView

The most interesting aspect of the Todo List is how a single database write propagates all the way to a smooth animated list update without any manual refresh logic. Room returns a LiveData<List<Task>> from the DAO — meaning every time the tasks table changes, Room automatically re-runs the query and emits the new list. The observer receives it, calls submitList(), and DiffCallback computes the minimum set of changes needed to get from the old list to the new one.

DB write Room re-queries LiveData emits Observer fires submitList() DiffCallback → animation

When a task is marked complete the DB query re-runs with ORDER BY isCompleted, priority DESC, dueDate — the completed task now sorts to the bottom. DiffCallback identifies the item by ID (areItemsTheSame), sees its contents changed (areContentsTheSame returns false), and tells RecyclerView to rebind and animate it to its new position simultaneously. The strikethrough and checkbox update happens at the same time as the slide animation.

Completing a task — animates to sorted position
02
Navigation & Persistence
Safe Args, Room, and TypeConverters

Navigation component manages the back stack between the task list, task detail, and add task screens. The nav graph declares taskId: Long as a required argument for TaskDetailFragment — Safe Args generates type-safe Directions and Args classes at compile time, so a missing or wrong-type argument is a compile error rather than a runtime crash.

TypeConverters handle the Date field on Task — SQLite has no native date type, so Room calls the converter to serialize Date to a Long (milliseconds since epoch) on write and deserialize back on read. This happens transparently — the DAO and ViewModel work with Date objects and never see the raw Long.

Immutable updates — tasks are never mutated directly. The detail fragment fetches the current task, calls copy() with the edited fields, and passes the new object to taskViewModel.update(). The original task's ID is preserved automatically.

Kotlin nav_graph.xml — Safe Args argument declaration
<!-- taskId declared as required Long argument -->
<fragment
    android:id="@+id/taskDetailFragment"
    android:name="...TaskDetailFragment">
    <argument
        android:name="taskId"
        app:argType="long" />
</fragment>

// Type-safe navigation — missing taskId = compile error
val action = TaskListFragmentDirections
    .actionTaskListFragmentToTaskDetailFragment(
        taskId = task.id
    )
findNavController().navigate(action)

// Retrieve in destination fragment
val args: TaskDetailFragmentArgs by navArgs()
val taskId = args.taskId
Kotlin Task update — immutable copy()
// copy() preserves id and any unspecified fields —
// only the listed fields are replaced
val updatedTask = it.copy(
    title = title,
    description = description,
    dueDate = dueDate,
    priority = priority,
    isCompleted = isCompleted
)
taskViewModel.update(updatedTask)
03
RecyclerView Adapter
ListAdapter and DiffCallback

TaskAdapter extends ListAdapter rather than the base RecyclerView.Adapter. ListAdapter manages its own internal list and runs DiffCallback on a background thread whenever submitList() is called.

DiffCallback uses two methods — areItemsTheSame matches items by id to establish identity across the old and new list, then areContentsTheSame compares matched pairs to check if any data changed. Since Task is a data class, == compares all fields automatically.

Click handling is kept out of the adapter entirely — the adapter constructor takes two lambdas (onTaskClick and onCheckboxClick) and the fragment decides what happens, keeping the adapter reusable.

Kotlin TaskDiffCallback.kt
class TaskDiffCallback : DiffUtil.ItemCallback<Task>() {

    override fun areItemsTheSame(
        oldItem: Task, newItem: Task
    ): Boolean {
        // Same entity? Match by stable ID
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(
        oldItem: Task, newItem: Task
    ): Boolean {
        // Data class == compares all fields automatically
        return oldItem == newItem
    }
}
Smooth Transition
Any change — whether adding, editing, or deleting a task — animates smoothly when returning to the task list rather than snapping to the new state instantly.
Deleting a task — removal animates on return to the list
← Previous Traffic Intersection Next → Maze Search