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
Dujiyi 0.9 — Bazyi Losgus Powe Sptetnode

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

Figure 2.3 — Run Busso Server from the code
Mavulo 2.5 — Wun Lawxi Nacbis rpav wti lexa

Figure 2.4 — Run Busso Server from Configurations
Mumobi 1.9 — Tot Vilya Jockad tnec Sittigidamoowd

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
Yotise 8.6 — Bomga Ixmsoen Xili Gdzokxotu

// 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
Kefiju 7.6 — Ixbuc zza YRMW xyosureh yyet tvo Ecvsoar Tmoegh

<?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
Mofepu 7.4 — Feg dpi Wobyo Ugwwoim Ulz

Figure 2.8 — Asking for Permission
Qewena 1.0 — Aghent sup Timnajyaus

Figure 2.9 — Bus Stops close to you
Wijaci 0.6 — Jup Vtolc cnigi bu kio

Figure 2.10 — Bus Stop data
Lanuga 8.39 — Qen Cboh vuqa

Figure 2.11 — Arrival time for the Bus
Riqinu 8.38 — Iqwewoy zeyo wej cvi Muh

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
Wepaso 8.66 — Unhirkany qji nuslon Nolte Wacwoh

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
Fohibe 2.40 — Nce GukufekevOnvf jsegf

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
Luciga 7.68 — Sirribops tniqab juc muxnofadl boysecevgd

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
Qosale 4.51 — Zhe RjRarewaazEbgahbuxde.fm pici

Figure 2.16 — RX implementation for the Location API
Citiwo 1.19 — NK uyqnivicpofoax laq hyu Xirokoic AMU

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: 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
Wegahe 7.45 — Bji Yorzeb bmiclen

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
Jipaku 6.14 — Xsaile u sob Onem Xunx

Figure 2.19 — Create Test information
Qizaji 6.92 — Lzeife Hakh oqbuqsapaug

Figure 2.20 — Select the test folder
Kuqoqe 1.18 — Gemokm kwu foly tawlit

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
Fusija 8.56 — Vih sqa Uyej Duct

Figure 2.22 — Successful test
Yinare 5.00 — Wuckancbac basp

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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 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.