Home Android & Kotlin Books Dagger by Tutorials

2
Meet the Busso App Written by Massimo Carli

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

In the first chapter of this book, you learned what dependency means, what the different types of dependencies are and how they’re represented in code. You learned details about:

  • Implementation Inheritance
  • Composition
  • Aggregation
  • Interface Inheritance

You saw examples of each type of dependency and you understood which works better in various situations. Using UML diagrams, you also learned why dependency is something you need to control if you want your code to be maintainable. You saw why applying these principles using design patterns is important to make your code testable.

So far, this book has contained a lot of theory with many concepts you need to master if you want to successfully use libraries like Dagger or Hilt for Dependency Injection (DI) on Android. Now, it’s time to move beyond theory and start coding.

In this chapter, you’ll get to know the Busso App, which you’ll work on and improve throughout this book. It’s a client-server app where the server is implemented using Ktor.

You’ll start by installing the server locally, or just using the pre-installed version on Heroku. Then you’ll configure, build and run the Busso Android App.

The version of the app you start with is basic, not something to be proud of. You’ll spend the last part of the chapter understanding why and taking the first steps to improve it.

The Busso App

Throughout this book, you’ll implement the Busso App, which allows you to find bus stops near you and information about arrival times. The app is available in the materials section of this book and consists of a server part and a client part. It uses a simple, classic client-server architecture, as you see in Figure 2.1:

Figure 2.1 — Client Server Architecture
Figure 2.1 — Client Server Architecture

The UML diagram in Figure 2.1 is a deployment diagram that shows you many interesting bits of information:

  1. The big boxes represent physical machines like computers, devices or servers. You call them nodes.
  2. The boxes with the two small rectangles on the left edge are components. The Busso App component lives in the device while the Busso Server lives on a server machine, probably in the cloud.
  3. The Busso Server exposes an interface you represent using something called a lollipop. You can use a label to give information about the specific protocol used in the communication — in this case, HTTP.
  4. The Busso App interacts with the HTTP interface the Busso Server provides. Represent this with a semicircle embracing the lollipop.

Before going into the details of these components, run the app using the following steps.

Installing and running the Busso Server

Busso Server uses Ktor, which you can open using IntelliJ.

Figure 2.2 — Busso Server File Structure
Rajeju 2.8 — Lolne Dojbum Fomo Xkfurgoci

fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

Figure 2.3 — Run Busso Server from the code
Lejoko 2.8 — Sec Feyhu Cikzec kdop hbe vote

Figure 2.4 — Run Busso Server from Configurations
Zazoke 7.8 — Gik Zadfe Vowbab ymeb Jebpinawuwiuls

2020-07-30 01:12:01.177 [main] INFO  Application - No ktor.deployment.watch patterns specified, automatic reload is not active
2020-07-30 01:12:03.320 [main] INFO  Application - Responding at http://0.0.0.0:8080

Building and running the Busso Android App

In the previous section, you started the Busso Server. Now it’s time to build and run the Busso Android App. For this, you need to:

Defining the server address

Use Android Studio to open the Busso project in the starter folder of the material for this chapter. You’ll see the file structure in Figure 2.5.

Figure 2.5 — Busso Android File Structure
Qebumi 2.8 — Didme Utlguef Ruya Cbgevbihi

// INSERT THE IP FOR YOUR SERVER HERE
const val BUSSO_SERVER_BASE_URL = "http://<YOUR SERVER IP>:8080/api/v1/"
# ipconfig getifaddr en0
# ipconfig getifaddr en1
# 192.168.1.124
// INSERT THE IP FOR YOUR SERVER HERE
const val BUSSO_SERVER_BASE_URL = "http://192.168.1.124:8080/api/v1/"

Configuring network security

As you can see, the local server uses the HTTP protocol, which requires additional configuration on the client side. Locate and open network_security_config.xml as a resource of XML type, like in Figure 2.6:

Figure 2.6 — Allow the HTTP protocol from the Android Client
Fetimu 5.4 — Ogrup yha NVSD dforexej sgiw xro Ivfroeh Draamj

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <domain-config cleartextTrafficPermitted="true">
    <domain includeSubdomains="true"><!-- YOUR SERVER IP --></domain>
  </domain-config>
</network-security-config>
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <domain-config cleartextTrafficPermitted="true">
    <domain includeSubdomains="true">192.168.1.124</domain>
  </domain-config>
</network-security-config>

Building and running the app

Now, you can run the app using an emulator or a real device by selecting the arrow shown in Figure 2.7:

Figure 2.7 — Run the Busso Android App
Namotu 8.6 — Hiz ztu Lekvo Utppuat Exs

Figure 2.8 — Asking for Permission
Refifi 4.8 — Ibverw tej Mefjaxxuiv

Figure 2.9 — Bus Stops close to you
Xuvuve 7.9 — Tev Zculp kxega ne yie

Figure 2.10 — Bus Stop data
Feboye 9.32 — Gih Krez qegu

Figure 2.11 — Arrival time for the Bus
Kosudi 6.61 — Ahwejom lazi tab gbi Fac

Running the Busso Server on Heroku

As mentioned earlier, you might not want to build and run the Busso Server on your own machine. Instead, you can use a running app that’s available on Heroku at the following address:

https://busso-server.herokuapp.com/
Figure 2.12 — Accessing the public Busso Server
Cowoqe 7.53 — Egsojnijd kwu nawnig Safvo Carxut

Configuring the Busso App for the Heroku server

To use the server installation on Heroku, you need to enter the following code into Configuration.kt:

const val BUSSO_SERVER_BASE_URL = "https://busso-server.herokuapp.com/api/v1/"
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <domain-config cleartextTrafficPermitted="true">
    <domain includeSubdomains="true">0.0.0.0</domain>
  </domain-config>
</network-security-config>

Improving the Busso App

Do you like the Busso App? Well, it works, but you can’t say the quality is the best. But what are the problems, and how can you fix them?

Reducing repetition

SplashActivity.kt contains the following code:

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  makeFullScreen()
  setContentView(R.layout.activity_splash)
  // 1
  locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
  // 2
  locationObservable = provideRxLocationObservable(locationManager, permissionChecker)
  // 3
  navigator = NavigatorImpl(this)
}
override fun onAttach(context: Context) {
  super.onAttach(context)
  // 1
  locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
  // 2
  locationObservable = provideRxLocationObservable(locationManager, grantedPermissionChecker)
  navigator = NavigatorImpl(context as Activity)
}

Taking scope and lifecycle into consideration

In any Android app, all the other components of the same app should share some objects, while other objects should exist while a specific Activity or Fragment exists. This is the fundamental concept of scope, which you’ll learn in detail in the following chapters. Scope is a vital part of resource management.

Adding application scope

Look at useLocation() in BusStopFragment.kt:

private fun useLocation(location: GeoLocation) {
  context?.let { ctx ->
    disposables.add(
        provideBussoEndPoint(ctx) // HERE
            .findBusStopByLocation(location.latitude, location.longitude, 500)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .map(::mapBusStop)
            .subscribe(busStopAdapter::submitList, ::handleBusStopError)
    )
  }
}
private fun getBusArrivals() {
  val busStopId = arguments?.getString(BUS_STOP_ID) ?: ""
  context?.let { ctx ->
    disposables.add(
        provideBussoEndPoint(ctx)
            .findArrivals(busStopId)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .map(::mapBusArrivals)
            .subscribe(::handleBusArrival, ::handleBusArrivalError)
    )
  }
}

Adding activity scope

Other objects should have a different lifecycle, such as the Navigator implementation in NavigatorImpl.kt located in libs/ui/navigation, as shown in Figure 2.13:

Figure 2.13 — The NavigatorImpl class
Vigiha 9.45 — Pyi DonufaruhOpgs ntowz

class NavigatorImpl(private val activity: Activity) : Navigator {
  override fun navigateTo(destination: Destination, params: Bundle?) {
    // ...
  }
}
override fun onAttach(context: Context) {
  super.onAttach(context)
  locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
  locationObservable = provideRxLocationObservable(locationManager, grantedPermissionChecker)
  navigator = NavigatorImpl(context as Activity) // HERE
}

The importance of scope

The concept of scope is fundamental, and you’ll read a lot about it in the following chapters.

Figure 2.14 — Different scopes for different components
Rufere 8.67 — Petsemopy khamom biv zaqxifost xahboticfz

Adding unit tests

The current implementation of the Busso App doesn’t contain any unit tests at all. What a shame! Unit tests are not only good for identifying regression, they’re also fundamental tools for writing better code.

private fun useLocation(location: GeoLocation) {
  context?.let { ctx ->
    disposables.add(
        provideBussoEndPoint(ctx)
            .findBusStopByLocation(location.latitude, location.longitude, 500)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .map(::mapBusStop)
            .subscribe(busStopAdapter::submitList, ::handleBusStopError)
    )
  }
}

The Rx Module for location

The Busso App uses a module that provides location data using the RxJava library. It’s useful to look at this library before continuing the journey into dependency injection. It’s the module located in the directory structure in Figure 2.15.

Figure 2.15 — The RxLocationObservable.kt file
Dihoye 8.45 — Txi XxLozagoatOxvajhoqco.jn tofe

Figure 2.16 — RX implementation for the Location API
Nazolo 1.51 — KF irsyixepzuqueb zil vlu Komuwaup AYA

data class GeoLocation(
    val latitude: Double,
    val longitude: Double
)
interface GeoLocationPermissionChecker {
  val isPermissionGiven: Boolean
}

Testing the RxLocation module

The Rx module is fairly well tested. Check it out by looking in the test folder under RxLocationObservableKtTest.kt and taking a quick look at the following test:

@Test
fun whenPermissionIsDeniedLocationPermissionRequestIsSentAndThenCompletes() {
  rxLocationTest(context) {
    Given {
      permissionIsDenied()
    }
    When {
      subscribeRx()
    }
    Then {
      permissionRequestIsFired()
      isComplete()
    }
  }
}

Challenge

Challenge 1: Some unit tests as a warm-up

After building and running the Busso App, it’s time for a nice challenge. As you know, the Busso App doesn’t have unit tests. Can you write some for the code related to the BusStopMapper.kt and BusArrivalMapper.kt files, as shown in Figure 2.17?

Figure 2.17 — The Mapper classes
Vohozu 1.56 — Tli Leksom wmecfoq

Challenge solution: Some unit tests as a warm-up

BusStopMapper.kt contains mapBusStop(), which you use to convert a BusStop model into a BusStopViewModel. What’s the difference?

data class BusStop(
    val id: String,
    val name: String,
    val location: GeoLocation,
    val direction: String?,
    val indicator: String?,
    val distance: Float?
)
data class BusStopViewModel(
    val stopId: String,
    val stopName: String,
    val stopDirection: String,
    val stopIndicator: String,
    val stopDistance: String
)
Figure 2.18 — Create a new Unit Test
Mocose 7.89 — Xziusa i fuw Ohay Zoxs

Figure 2.19 — Create Test information
Makita 4.71 — Gsuepu Dohz akyomporuut

Figure 2.20 — Select the test folder
Palabo 5.16 — Watehs hla wosz zukmef

class BusStopMapperKtTest {
  @Test
  fun mapBusStop() {
  }

  @Test
  fun testMapBusStop() {
  }
}
@Test
fun mapBusStop_givenCompleteBusStop_returnsCompleteBusStopViewModel() {
  // 1
  val inputBusStop = BusStop(
      "id",
      "stopName",
      GeoLocation(1.0, 2.0),
      "direction",
      "indicator",
      123F
  )
  // 2
  val expectedViewModel = BusStopViewModel(
      "id",
      "stopName",
      "direction",
      "indicator",
      "123 m"
  )
  // 3
  assertEquals(expectedViewModel, mapBusStop(inputBusStop))
}
Figure 2.21 — Run the Unit Test
Keqizo 3.52 — Gep bzo Ebun Vukn

Figure 2.22 — Successful test
Vezawe 3.33 — Faksaqqcac seby

Key points

  • The Busso App is a client-server app.
  • The Busso Server has been implemented with Ktor. You can run it locally or use the existing Heroku installation.
  • The Busso App works, but you can improve it by removing code duplication and adding unit tests.
  • The concept of scope or lifecycle is fundamental and you’ll learn much more about it throughout this book.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

© 2022 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.