For the More Curious: Managing Dependencies

PhotoRepository provides a layer of abstraction over the source of Flickr photo metadata. Other components (such as PhotoGalleryFragment) use this abstraction to fetch Flickr data without worrying about where the data is coming from.

PhotoRepository itself does not know how to download JSON data from Flickr. Instead, PhotoRepository relies on FlickrApi to know the endpoint URL, to connect to that endpoint, and to perform the actual work of downloading the JSON data. PhotoRepository is said to have a dependency on FlickrApi.

You are initializing FlickrApi inside the PhotoRepository init block:

    class PhotoRepository {
        ...
        init {
            val retrofit: Retrofit = Retrofit.Builder()
                .baseUrl("https://api.flickr.com/")
                .addConverterFactory(MoshiConverterFactory.create())
                .build()
            flickrApi = retrofit.create()
        }
        ...
    }

This works well for a simple application, but there are a few potential issues to consider.

First, it is difficult to unit test PhotoRepository. Recall from Chapter 6 that the goal of a unit test is to verify the behavior of a class and its interactions with other classes. To properly unit test PhotoRepository, you need to isolate it from the real FlickrApi. But this is a difficult – if not impossible – task, because FlickrApi is initialized inside the PhotoRepository init block.

Hence, there is no way to provide a mock instance of FlickrApi to PhotoRepository for testing purposes. This is problematic, because any test you run against fetchPhotos() will result in a network request. The success of your tests would be dependent on network state and the availability of the Flickr back-end API at the time of running the test.

Another issue is that FlickrApi is tedious to instantiate. You must build and configure an instance of Retrofit before you can build an instance of FlickrApi. This implementation requires you to duplicate five lines of Retrofit configuration code anywhere you want to create a FlickrApi instance.

Finally, creating a new instance of FlickrApi everywhere you want to use it results in unnecessary object creation. Object creation is expensive relative to the scarce resources available on a mobile device. Whenever practical, you should share instances of a class across your app and avoid needless object allocation. FlickrApi is a perfect candidate for sharing, since there is no variable instance state.

Dependency injection (or DI) is a design pattern that addresses these issues by centralizing the logic for creating dependencies, such as FlickrApi, and supplying the dependencies to the classes that need them. By applying DI to PhotoGallery, you could easily pass an instance of FlickrApi into PhotoRepository each time a new instance of PhotoRepository was constructed. Using DI would allow you to:

  • encapsulate the initialization logic of FlickrApi into a common place outside of PhotoRepository

  • use a singleton instance of FlickrApi throughout the app

  • substitute a mock version of FlickrApi when unit testing

Applying the DI pattern to PhotoRepository might look something like this:

    class PhotoRepository(private val flickrApi: FlickrApi) {
        suspend fun fetchPhotos(): List<GalleryItem> =
            flickrApi.fetchPhotos().photos.galleryItems
    }

Note that DI does not enforce the singleton pattern for all dependencies. PhotoRepository is passed an instance of FlickrApi on construction. This mechanism for constructing PhotoRepository gives you the flexibility to provide a new instance or a shared instance of FlickrApi based on your use case.

DI is a broad topic with many facets that extend well beyond Android. This section just scratches the surface. There are entire books dedicated to the concept of DI and many libraries to make DI easier to implement. If you want to use DI in your app, you should consider using one of these libraries. It will help guide you through the process of DI and reduce the amount of code you need to write to implement the pattern.

At the time of this writing, Dagger 2 and its companion Hilt are the official Google-recommended libraries for implementing DI on Android. You can find detailed documentation, code samples, and tutorials about DI on Android at dagger.dev/​hilt.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset