Null Context when trying to build GoogleApiClient in an Activity

1,244 views
Skip to first unread message

Austin Guest

unread,
Apr 23, 2015, 12:59:22 PM4/23/15
to mac...@googlegroups.com
Hi there. I'm a somewhat experienced Scala dev who's *very* new to Android development and Macroid, but am very excited to learn more, stick around and contribute, so hoping folks will bear with my n00b-ishness for a sec.

I'm making a simple location-sharing Android app, and step 1 is to make an Activity in which the user clicks a button and the app shows them their lat/lon. I've worked through all the Macroid docs and the first few sections of the Android API and was trying to translate this example java code from Google into the context of a Macroid app.

This is what I've got so far (also pasted below my signature). I'm still a bit hazy on how to handle slots and passing completed futures into the Ui code, so that code might be futzy, but what I'm really focused on at the moment is the call to `buildClient` on line 58:

def buildClient: Option[GoogleApiClient] = {
this.synchronized {
Some(
new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build()) } }

Currently, that function is triggering the following runtime error, which crashes the app:

java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Looper android.content.Context.getMainLooper()' on a null object reference

As best I can tell that's because there's some sort of friction between how I'm including the context in Macroid (which I assume is using implicits):

class MainActivity extends Activity with Contexts[Activity] 
with
ConnectionCallbacks with OnConnectionFailedListener {


and how Java wants to look for them when it uses the Activity instance to pass to the API client user. But once I get to that depth, I'm lost.

Can anyone help point me in the right direction?

Best,

Austin

PS: Here's the full code for the Activity in question:

package org.tlc.whereat.components.activities

import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.widget.{Button, LinearLayout, TextView}
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.common.api.GoogleApiClient.{ConnectionCallbacks, OnConnectionFailedListener}
import com.google.android.gms.location.LocationServices
import macroid.Contexts
import macroid.FullDsl._
import org.tlc.whereat.ui.tweaks.MainTweaks

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Promise


/**
* Author: @aguestuser
* Date: 4/22/15
* License: GPLv2 (https://www.gnu.org/licenses/gpl-2.0.html)
*/


class MainActivity extends Activity with Contexts[Activity] with ConnectionCallbacks with OnConnectionFailedListener { // include implicit contexts

var context = activityActivityContext
var apiClient: Option[GoogleApiClient] = None
var locView: Option[TextView] = slot[TextView]
val locText: Promise[String] = Promise[String]()

override protected def onCreate(savedInstanceState: Bundle): Unit = {
super .onCreate(savedInstanceState)

apiClient = buildClient

setContentView {
getUi {
l[LinearLayout](
w[Button] <~
text("Get Location") <~
On.click {
locView <~ show
},
w[TextView] <~
wire(locView) <~
locText.future.map(text)
) <~ MainTweaks.orient } } }

override protected def onStart(): Unit = {
apiClient.get.connect() }

override protected def onStop(): Unit = {
super.onStop()
if (apiClient.get.isConnected) apiClient.get.disconnect() }

def buildClient: Option[GoogleApiClient] = {
this.synchronized {
Some(
new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build()) } }

override def onConnected(connectionHint: Bundle): Unit = {
val loc = LocationServices.FusedLocationApi.getLastLocation(apiClient.get)
if (loc != null) locText.success{ s"Lat: ${loc.getLatitude}, Lon: ${loc.getLongitude}" }
else toast("No location detected!") <~long <~fry }

override def onConnectionFailed(res: ConnectionResult): Unit = {
Log.i("whereat", "Connection failed: ConnectionResult.getErrorCode() = " + res.getErrorCode) }

override def onConnectionSuspended(cause: Int) {
Log.i("whereat", "Connection suspended")
apiClient.get.connect() }

}


Nick Stanchenko

unread,
Apr 23, 2015, 8:23:44 PM4/23/15
to mac...@googlegroups.com
Hi Austin,

I don’t see anything in your code that would cause that error... Is there any stack trace that you could provide? I wouldn’t be able to try to reproduce it myself until the weekend.
Some things I did notice:
  • Unless I’m missing something the line “var context = activityActivityContext” is not needed.
  • You could do “apiClient.foreach(_.connect())” and “apiClient.filter(_.isConnected).foreach(_.disconnect())” — I think it’s a bit more idiomatic and robust.
  • I would replace “locText.success” with “locText.trySuccess”, since the promise is write-once and it might already be fulfilled.
  • You need to enclose “toast(...) <~ ... <~ fry” into runUi (like in the examples at http://macroid.github.io/guide/ToastsDialogs.html#toasts)
Maybe you could debug the issue by adding extra logging statements when creating the API client, etc.

Nick

Austin Guest

unread,
Apr 23, 2015, 8:50:26 PM4/23/15
to mac...@googlegroups.com
Thanks so much for getting back so quickly and helpfully! :)

As for the bug causing the runtime error, I sniffed it out! It was the `this` before the `synchronized` block in the APIClient builder. The working version looks like this:

protected def buildClient: Option[GoogleApiClient] = {

synchronized {
Some(
new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build()) } }

Thanks, too for the suggestion on the (very smelly!) `apiClient.get` lines. Agree what you suggest is clearly better. Since we're talking, perhaps you could talk me through how to think about (or where to [re]read more docs on) how to pass asynchronously obtained data into a Ui brick.

For example, right now, I can pass one (and -- as you rightly point out -- only one!) location into the `locView` slot. Worse: it shows up as soon as the promise is completed (almost immediately upon load), *not* after the user clicks. It would be nice if (1) the call to the location API was a function returning a Future[Location] that is triggered by the click, and could be called as many times as the user clicks (in fact: this will be *hugely* important for the app's stated purpose -- allowing protesters to communicate frequently during a march.)

Right now, I'm using a `Promise[String]` as a placeholder since it wasn't immediately apparent to me, how I could have an asynchronous function triggered by the user's click. (That doesn't seem to be what `On.click` is for -- or at least I can't find any examples in the docs of it being used that way.) Eventually, I want to take the lat/lon returned from the LocationService API and reverse geocode it to street corners with a GET request to this service, preferably using Dispatch or some other HTTP library that returns a Future[Request] (assuming that's possible in an Android context).

So ideally, (2) the function that triggers the execution of all the chained futures (that get the location and then reverse geocode them) would be triggered by a click, and the UI brick that displays the result wouldn't be shown until the chain of futures had completed, at which point it would display the value wrapped inside the pipeline of Futures.

It's not immediately apparent to me right now how I would do either (1) or (2). (It's clear to me how to get `On.click { ... } ` to trigger the execution of tweaks (including showing and hiding), but that's not what I want.  It *seems* like what I'm is triggering a chain of functions that ultimately trigger tweaks... Is that right? If so, how would I do that?

Regardless: where should I look to figure out how to get this right?

In case it's useful, this is the most relevant part of the code for the purposes of this question:

...

w[Button] <~
text("Get Location") <~
On.click {
locView <~ show
},
w[TextView] <~
wire(locView) <~
locText.future.map(text)
) <~ MainTweaks.orient
...

Austin Guest

unread,
Apr 23, 2015, 8:52:12 PM4/23/15
to mac...@googlegroups.com
Whoops! Meant: HTTP library that returns a *Future[Response]*

Nick Stanchenko

unread,
Apr 23, 2015, 10:00:26 PM4/23/15
to mac...@googlegroups.com
Glad you got it working :)

As for your follow-up questions,
  1. IIRC you can use `getLastLocation` to get the location, which would return immediately. Of course it can also return `null`, but you can handle that case separately. So this means your method will return Option[Location], not Future[Location]. The only thing you might want to wait for (and thus make it a Future) here is the connection. In that case you can do something like this:
    var connectionPromise = Promise[Unit]()

    // recreate the promise
    def buildClient(...) = { ...; connectionPromise = Promise() }

    // fulfill the promise when the connection is made
    def onConnected(...) = { ...; connectionPromise.trySuccess(()) }

    // empty the promise once disconnected
    def onDisconnected(...) = { ...; connectionPromise = Promise() }

    def getLocation: Future[Option[Location]] = connectionPromise.future
     
    .map(_ => apiClient.flatMap(client
    => Option(LocationServices.FusedLocationApi.getLastLocation(client))))

  2. Once you have that first function, you can combine it with your geocoding logic:
    def geocodeLocation(location: Location): Future[Response] = ...

    def parseGeocodingResponse(response: Response): String = ...

    def getGeocode: Future[String] = getLocation flatMap {
     
    case Some(l) => geocodeLocation(l)
     
    case None => ...
    } map parseGeocodingResponse

  3. Finally, you can easily wire that logic to On.click. What is happening here is that you map Future[String] to a Future[Tweak[View]] and Macroid knows how to tweak a widget with a Future[Tweak[...]]: it just waits until the future is ready and applies the result.
    ...
    w
    [Button] <~
     
    On.click {
         locView
    <~ getGeocode.map(code => text(code) + show)
     
    }
    ...
Hope this addresses your questions,
Nick

Austin Guest

unread,
Apr 23, 2015, 11:42:17 PM4/23/15
to Nick Stanchenko, mac...@googlegroups.com
Thanks so much Nick! What a *wonderful* warm welcome to the ecosystem! :D Got working code (just the location retriever for now) up and running (also pasted below).

Last thing on the checklist to feel like I can really dive in: TESTS! How do people in the Macroid community like to write tests?

I've seen that folks are fond of RoboElectric and that people have ported it to specs2 and to ScalaTest. The former seems a bit outdated (though specs2 is normally my go to). Are there any other (perhaps more straightforward?) ways to test Scala Android code that I'm not seeing?

/a/

class MainActivity extends Activity with Contexts[Activity] with ConnectionCallbacks with OnConnectionFailedListener { // include implicit contexts

  var apiClient: Option[GoogleApiClient] = None
var locView: Option[TextView] = slot[TextView]
  var connectionPromise = Promise[Unit]()

  override protected def onCreate(savedInstanceState: Bundle): Unit = {
super .onCreate(savedInstanceState)

apiClient = buildClient

setContentView {
getUi {
l[LinearLayout](
w[Button] <~
text("Get Location") <~
            On.click { locView <~ getLocation.map { l ⇒ text(parseLocation(l)) + show } },
w[TextView] <~
wire(locView) <~ hide
        ) <~ MainTweaks.orient } } }

override protected def onStart(): Unit = {
    super.onStart()
apiClient foreach { _.connect } }


override protected def onStop(): Unit = {
super.onStop()
    apiClient foreach { cl ⇒ if(cl.isConnected) cl.disconnect() } }


protected def buildClient: Option[GoogleApiClient] = {
    connectionPromise = Promise()

synchronized {
Some(
new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build()) } }

  override def onConnected(connectionHint: Bundle): Unit =
    connectionPromise.trySuccess(())


override def onConnectionFailed(res: ConnectionResult): Unit = {
    connectionPromise = Promise()

Log.i("whereat", "Connection failed: ConnectionResult.getErrorCode() = " + res.getErrorCode) }

override def onConnectionSuspended(cause: Int) {
    connectionPromise = Promise()

Log.i("whereat", "Connection suspended")
    apiClient foreach { _.connect } }

private def getLocation: Future[Option[Location]] =
connectionPromise.future map { _ ⇒
apiClient flatMap { cl ⇒
Option(LocationServices.FusedLocationApi.getLastLocation(cl)) } }

private def parseLocation(l: Option[Location]): String = l match {
case Some(ll) ⇒ s"Lat: ${ll.getLatitude}, Lon: ${ll.getLongitude}"
case None ⇒ "Location not available" }

}





--
You received this message because you are subscribed to a topic in the Google Groups "macroid" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/macroid/JiudPc3xClE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to macroid+u...@googlegroups.com.
To post to this group, send email to mac...@googlegroups.com.
Visit this group at http://groups.google.com/group/macroid.
To view this discussion on the web visit https://groups.google.com/d/msgid/macroid/a610c2e6-20d2-4199-b831-7846a05a9f21%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--

Austin Guest

unread,
Apr 24, 2015, 12:55:57 PM4/24/15
to Nick Stanchenko, mac...@googlegroups.com
Also a bit curious: how do I know that the call to `getLocation` doesn't execute in the UI thread?

On.click { locView <~ getLocation.map { l ⇒ text(parseLocation(l)) + show } }
I'm not using `mapUi` as recommended here. How, in general, when working with Futures, do I make sure my code isn't executing on the UI thread?

(Also happy to move this -- or the question about testing frameworks -- to a separate thread, as it clearly has almost nothing to do with my original question!)

/a/

Nick Stanchenko

unread,
Apr 24, 2015, 7:17:15 PM4/24/15
to mac...@googlegroups.com, nick....@gmail.com
Hi,

The `map` operation in that line, as well as the other `map`s and `flatMap`s inside `getLocation`, operate on the implicit execution context, in your case — scala.concurrent.ExecutionContext.Implicits.global. So all the async calls will be by default scheduled outside the UI thread. You can achieve the opposite effect, i.e. schedule a Future on the UI thread, by passing macroid.UiThreadExecutionContext as the execution context. Once the Future is ready, Macroid will ensure that the tweaks are applied on the UI thread (this is one of the jobs of the `<~` operator).

I’ll reply to your other question in the other thread :)

Nick
...
Reply all
Reply to author
Forward
0 new messages