Pokemon Tracker is an android app that allows users to search, favorite, and view pokemon!
Screenshots of the app with view descriptions located in this album
This is the general app flow:
- When the user starts the app, they enter the title activity.
- After tapping the title activity screen they enter the tracker activity which will create a
search fragment once.
- The tracker uses a tab navigation layout with an app bar at the top of the view.
- Within the tracker activity, the user can switch between the Search and Favorites tab
and corresponding fragments by either tapping the tab or swiping horizontally.
- Upon navigating to the favorites tab, the fragment will be created once and reused.
- The user can select a theme using the toolbar at the top, which will be persisted using SharedPreferences. The activity will be restarted with the new theme applied and a snackbar confirmation message will be shown.
- The search fragment renders a list of pokemon using a RecyclerView. Each element utilizes a card layout with nested cards for pokemon overview information. Upon tapping an element, a dialog fragment is created which will display more detailed information about the pokemon selected.
- Inside the detail view, the user can favorite a pokemon. They can tap outside of the dialog to exit the detail view.
- Inside the Favorites tab, the user can view a list of all of the pokemon they favorited. They can re-enter the detail view and unfavorite a pokemon here by tapping on a sprite.
I integrated Koin annotations to create class instances with required dependencies.
- I used
@Factoryannotations on presenters since they can be created and recreated corresponding to the lifecycle of their fragments. I added@Singleannotations to the repositories and image processor class since they only need one instance.
The app uses data provided by the GraphQL DexAPI.
- Two queries are used to fetch data from the API. The first retrieves a list of overview information for each pokemon, and the other provides additional information about a single pokemon and is used on an as-needed basis by the app.
query PokemonOverview {
allPokemon {
name
nat_dex_num
weight
types {
name
}
sprites {
front_default
}
}
}
query PokemonDetail($id: Int!) {
pokemon(id: $id) {
pokedex_entries {
description
}
height
sprites {
back_default
}
}
}
I split up the queries this way instead of fetching all of the data at once because each of the 893 pokemon the API provides info for can have up to ~30 pokedex entries.
- I did not benchmark the reduction in time but there was a noticeable decrease of at least a few seconds for the initial overview fetch for me.
After a network request for overview or detailed data, the result is cached to a single table in a Room database.
- The app will try and find overview or detailed info about a pokemon from the Room database before performing a network request to facilitate offline usage.
- Both network requests and database queries are handled by the
PokemonRepository. SharedPreferences queries are handled by thePreferencesRepository.
Pokemon Tracker uses the MVP architectural pattern, with presenters and activities grouped into feature folders.
- This pattern separates business logic from each activity and fragment to a dedicated presenter. The presenter tells the view when to update the UI and communicates with the Model or data layers.
I used abstract superclasses for functionality shared across app activities and fragments.
- The
TitleActivityandTrackerActivityare derived from theBaseActivityclass. - My intention here was for
BaseActivityto apply app theme settings stored as
SharedPreferences to each activity on creation.
The presenters are ultimately derived from the BasePresenter class.
- This class stores references to the activity or fragment lifecycle each presenter is responsible for.
- This setup came in handy for me when I needed to launch coroutines associated with the view lifecycle.
The two primary fragments of TrackerActivity are subclasses of the abstract class
DetailFragment.
- This abstract class contains the functionality responsible for showing the pokemon detail modal view. The modal view itself is an Android dialog fragment with a custom view.
While researching I came across a variety of ways to set up base classes and encourage code reuse. At first, I tried to create interfaces for each activity and fragment to separate the interface from the implementation.
- Since this was a small application, I eventually opted to have just the implementation but have an extra extensible interface for snackbar messages.
The presenter logic for displaying the pokemon detail dialog is located inside the abstract class PokemonDetailPresenter.
- This abstract class also extends the
ImagePresenterclass which contains logic to load an image and extract a color palette from an image. - The search fragment uses a
RecyclerViewto display all pokemon queried from thePokemonRepository.
Similarly, the favorites fragment inherits from the PokemonDetailPresenter.
- It uses a grid layout to display sprites of all favorited pokemon.
- One part I didn't get to in time that I would like to keep in mind for next time is to propagate changes in the model to the search and favorites fragments. Right now, if you add or remove a favorite the UI will not immediately update inside the favorites fragment.
Finally, the detail dialog fragment inherits directly from the ImagePresenter
to display sprites inside the dialog.
- I refactored some of the hardcoded callbacks invoked after asynchronous image processing inside the
ImageProcessorwhile working on this fragment. - This allowed me to more easily set up the
ImagePresenterwhich all of the fragments inherit from. - For example, descendants of
ImagePresentercan now pass in their own callback functions upon finishing loading or constructing a palette from an image instead of relying on theImageProcessorto figure out what needs to be done with a processed image.
I used ConstraintLayouts for all of the activities and fragments, except the favorites fragment where I used a GridLayout.
I avoided using hardcoded values and tried to apply reusable styles except for app module level variables, which I had trouble overriding (ex: app:cardCornerRadius).
The color theme was randomly generated using Coolors.co










