Kotlin Talk: Fetching Data from the Web for Google Assistant

Joel Anderson
3 min readMar 18, 2019

--

Now that we have our boilerplate project built and functioning, let’s explore modifying our application to return meaningful data to our users.

For this demo, we’ll create a simple weather client that returns the current temperature in New York City, using the free API offered by MetaWeather, which provides us with current temperatures and other live and predictive weather data.

Part 2 — Fetching Web Data

Since our project’s build processes are automated by Gradle, just like in Android, we can employ the same dependencies that we’re already used to. Libraries such as OKHTTP and Retrofit work just as seamlessly as in Android Studio, so we’ll add those to our project to drastically simplify our networking logic. We’ll also include Dagger for Dependency Injection. For most Android developers, this should be well-trodden territory.

dependencies {
//Dagger =========================================
implementation("com.google.dagger:dagger:$dagger")
kapt("com.google.dagger:dagger-compiler:$dagger")

//Square =========================================
implementation("com.squareup.okhttp3:okhttp:$okhttp")
implementation("com.squareup.okhttp3:logging-interceptor:$okhttp")
implementation("com.squareup.retrofit2:retrofit:$retrofit")
}

Using Retrofit, we can create a Kotlin interface to interact with MetaWeather’s API. Since Google Assistant performs all of its actions on a single thread, we don’t have to worry about calling this API asynchronously. We can simply declare our @GET request as returning a Call , which we can execute() to await the result from the server on the main thread. In the case of a server exception, we’ll throw an IOException which we will handle in MyAssistantApp.

 @Singleton
class SimpleRepository @Inject constructor(
retrofit: Retrofit
) : IRepository {
private val api: API = retrofit.create(API::class.java)


@Throws(IOException::class)
override fun fetchNYCWeather(): WeatherResponse = api.getNYCWeather().execute().body() ?: throw IOException()

interface API {

@GET("api/location/2459115")
fun getNYCWeather(): Call<WeatherResponse>

}
}

interface IRepository {
@Throws(IOException::class)
fun fetchNYCWeather(): WeatherResponse
}

Next, we can create a Dagger module for our application that houses the singleton instances for Retrofit and our newly created Repository. Just like we’re used to in Android, we can now use Dagger to manage dependency injections across our application.

@Module
class AppModule {

@Provides
@Singleton
internal fun provideRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("https://www.metaweather.com/")
.client(OkHttpClient())
.build()

@Provides
@Singleton
internal fun provideDataStore(
retrofit: Retrofit
): IRepository = SimpleRepository(retrofit)

}

Finally, back in the MyAssistantAction.kt class, we will inject our Repository to make network requests and perform our own business logic on their responses. Here we can also catch the IOExceptions that we throw in our repository to ensure that networking errors are gracefully handled and displayed to the user.

class MyAssistantAction : DialogflowApp() {

val injector: AppComponent = DaggerAppComponent.builder().build()

@Inject
lateinit var repository: IRepository

init {
injector.inject(this)
}

@ForIntent("Default Welcome Intent")
fun welcome(request: ActionRequest): ActionResponse {
val builder = getResponseBuilder(request)
try {
val weatherResponse = repository.fetchNYCWeather()
builder.add(formatWeatherResponse(weatherResponse))
} catch (e: Exception) {
builder.add("Whoops, an error occurred!")
}
return builder.build()
}

private fun formatWeatherResponse(weatherResponse: WeatherResponse): String {
val tempCelsius = weatherResponse.consolidatedWeather.first().temp
val tempFahrenheit = (((tempCelsius * 9) / 5) + 32).toInt()
return "The temperature in New York City is $tempFahrenheit degrees Fahrenheit."
}
}

And just like that, our application is now able to hit an HTTP endpoint, retrieve the response, perform our business logic, and respond to the user in a friendly, easily understood manner. Now, when our users invoke our application, they’ll receive useful information in real-time!

And, since we used libraries common in Android development, we can likely reuse networking logic from our Android application in our Assistant application, without having to rewrite it entirely in a completely different language.

Next time, we’ll take our app even further and explore handling user input, allowing the user to request the current temperature by location and receive even more dynamic, personalized results.

--

--