Office Hours — Today, August 18

Tuesday, August 14

Aug 18
3:50 PM
Mark M.
has entered the room
Mark M.
turned off guest access
Mark M.
turned on guest access
3:55 PM
Mathias
has entered the room
Mathias
Hi Mark, hows it going
Mark M.
hello, Mathias!
OK, and you?
Mathias
Of all good. little rainy here in Stockholm
i thought i'd pop in and get some help with the upgrade to targetSdkLevel 26 we apparently have to do now.... :)
Mark M.
the clock is ticking on that
Mathias
yeah
4:00 PM
Mathias
got most stuff sorted, but have two things that i'm working on
i prepared a long(ish) text for some background, i'll paste it
it's about geofences and the background restrictions in Oreo
View paste
I talked to you a while back about needing to do an asynchronous call from my geofence trigger. Back then, i had everything in a broadcastreceiver that was triggered.

You explained to me that any process i kick off might get killed. So i created a Service, had the broadcastreceiver call my service.
In my service, i have OnStartcommand return START_NOT_STICKY, and call "stopSelf" when the process is done.

This has worked fine and would still do, if not for the "targetsdkversion 26" requirement.

Now, i got the dreaded "Background execution not allowed: receiving Intent", and my geofences were suddenly not triggered.

I read up, and apparently had to change to an IntentService, so now that is triggered instead, and it seems to be called.

However, and here is my question - i still have the need for background execution in the geofence trigger. 

Can i continue to kick off my background service as i did in the broadcastreceiver, only now from the intentservice?
Mark M.
"apparently had to change to an IntentService, so now that is triggered instead, and it seems to be called" -- do you mean a JobIntentService?
Mathias
hmm no its a "public class GeofenceIntentService extends IntentService {"
Mark M.
nothing really changes in Android 8.0 between a Service and an IntentService
you can use that, as well as you used the regular service, so long as it is a foreground service and you use startForegroundService()
Google is steering us towards JobIntentService and, eventually, WorkManager for background work
Mathias
well, it was the broadcastreceiver that triggered the "background execution not allowed". when i changed it from a receiver to an intentservice it started working. or am i missing something?
Mark M.
well, an IntentService is also in the background, unless you made it a foreground service
so I do not know how that fixed anything
Mathias
OK. So i need something to be triggered by the geofence. and in the geofence trigger i need to to a locationclient lookup of the current location. What would you suggest i do?
Mark M.
I have not used the geofence APIs -- how do you get control from those?
do you supply a PendingIntent or something?
4:05 PM
Mathias
yeah. you register the geofence like this:
View paste
Intent intent = new Intent(session, GeofenceIntentService.class);
            PendingIntent proximityIntent = PendingIntent.getService(session.getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
and then you register the geofence with that intent
Mark M.
OK, and when you say "it was the broadcastreceiver that triggered the "background execution not allowed"", what do you mean? do you mean that Play Services itself threw an exception when trying to execute your PendingIntent?
Mathias
No, i debugged the phone, used a fake location app to trigger geofences, i then got error logcats from the google geofence service in the phone
"Background execution not allowed: receiving Intent". I then had the geofence pendingintent registered with a broadcastreceiver. After i changed the code to use intentservice instead, it started working again
Mark M.
OK, I cannot explain that, other than as a bug in Play Services somewhere
Mathias
you mean that it started working with the intentservice?
Mark M.
yes
4:10 PM
Mathias
do you mean that it *should* work with a broadcastreceiver?
Mark M.
they should be equivalent -- either it should work from both or fail from both
the only scenario that I know of where there is a difference is if the IntentService is started with startForegroundService(), and you would be crashing if that IntentService did not call startForeground()
Mathias
ok. google has a page on geofences (https://developer.android.com/training/location...). Their examples are with intentservice
yeah, but i am not starting it, nor did i start the broadcastreceiver. it's started by the geofence service
Mark M.
I understand that -- starting a background thing from the background should behave the same, regardless of whether the "background thing" is a receiver or an IntentService
why are you not using a PendingIntent pointing to the "real" service, the one that your receiver used to start? why do you need to have the PendingIntent point to something else (receiver, IntentService)?
Mathias
O
sorry, im writing explanation
Mark M.
I thought you were naming an Android version :-)
4:15 PM
Mathias
i use geofences to remind people that they should check out when they go home. But i don't want to remind too often. So, only if person has worked X time, and not been reminded for X minutes for example, should i take location and check out. So, i do the "checks" in the broadcastreceiver, and only when i have to do "real work" i call the service, where i call locationclient, create notifications etc.
Mark M.
personally, I would just move all of that into the service
you are certainly welcome to stick with your current system if you want
Mathias
would be happy to. I think i didn't understand that i could register a geofence with a service
Mark M.
a PendingIntent starts an activity, starts a service, or sends a broadcast
it can start a regular Service or an IntentService
Mathias
ok
Mark M.
in either case, the real service will need to be a foreground service on Android 8.0+, most likely, or possibly a JobIntentService
Mathias
then i should move all to the service. And you think i can kick off a locationclient request from the service given the 8.0 crackdown on background stuff?
ok, which means that i can't do the locationclient call?? sounds strange
Mark M.
there are separate issues for "running code in the background" and "getting locations in the background"
Mathias
ok
what i am currently doing in the service is:
Mark M.
for running code in the background, if it is going to take more than a minute, you will need a foreground service or a JobIntentService
Mathias
1. take location
2. make a server call when location comes back
but in the context of a geofence trigger, what IS "foreground service"? we're already in the background?
4:20 PM
Mark M.
a foreground service is a service in which you call startForeground()
passing in a Notification
Mathias
ok
Mark M.
now, I have not played with geofences, let alone on Android 8.0+
according to https://developer.android.com/about/versions/or..., you should still get relatively frequent geofence triggers
how well you can get a location is somewhat unclear
Mathias
yeah, and the trigger works. my worry is just that i need to kick off proccesses for the location and server call without android killing my service
i guess i¨ll just move everything into a "normal" service and make that triggered by the geofence and see what happens
Mark M.
the general recommendation nowadays is a foreground service or a JobIntentService
Mathias
ok
so, next topic. Notifications!
4:25 PM
Mathias
tldr: i have grouped/stacked notifications. They have stopped working in sdkLevel 26.
Mark M.
is that yours
?
Mathias
:)
You're faster than Lucky Luke
Mark M.
I remembered seeing the question
I do not know what is going on there
you might try passing the channel ID to the NotificationCompat.Builder constructor, rather than calling setChannelId(), but I doubt that will help
Mathias
i can basucally just change "26" to "24" in intellij, rebuild and they start grouping
Mark M.
wait
you seem to be reusing CHANNEL_ID_EVENTS
that is the name of the NotificationChannel... and of a NotificationChannelGroup?
you are calling events.setGroup(CHANNEL_ID_EVENTS), implying that you have a group named CHANNEL_ID_EVENTS
Mathias
yeah. i didn't do that first, but i thought that every channel should be in a single group. is that wrong?
4:30 PM
Mathias
that is, 3 channels in one group each
Mark M.
I'd have the group use a different ID than the channel, if nothing else
you would get the same basic effect by not having groups, for that pattern
Mathias
exactly. i just set the group to the same as the channel to see if they started grouping. (narrator: "they did not")
Mark M.
but you didn't actually define the group?
Mathias
i can remove the group name from the channels
yes i do
Mark M.
OK
Aaron
has entered the room
Mark M.
the group for NotificationChannel just controls presentation in the Settings app, not actual notification functionality
Mathias
View paste (3 more lines)
/**
     * Prior to notifying about something, we should always greate a group first, so that notifications of a similar kind will be grouped together on 6.0 and up
     *
     * @return Notification the group notification.
     */
    private static Notification createNotificationGroup(Context context){
        Log.d(TAG, "createNotifdicationGroup!");
        NotificationCompat.Builder groupBuilder = new NotificationCompat.Builder(context).setSmallIcon(R.drawable.notification)
            .setGroupSummary(true).setGroup(NotificationTypeEnum.DEFAULT.getName()).setAutoCancel(true);
        groupBuilder.setContentTitle((context.getString(R.string.notification_group_title_default))).setContentText("");

        //this intent is so that the app is opened even if the group summary is pressed
        Intent groupIntent = new Intent(context, NubaMainTabActivity.class);
        PendingIntent pendingGroupIntent = PendingIntent.getActivity(context, NotificationTypeEnum.DEFAULT.val, groupIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        groupBuilder.setContentIntent(pendingGroupIntent);
...
Mark M.
(BTW, hello Aaron -- I will be with you shortly!)
Mathias
so, whenever i create a notofication of any kind, i create a grouped notification first
Aaron
no worries, thanks
Mark M.
that's not a NotificationChannelGroup
Mathias
no it's the group for the notification
Mark M.
the value passed to setGroup() of a NotificationChannel is supposed to be the ID of a NotificationChannelGroup
Mathias
wow... ok
Mark M.
that just controls headings in the Settings app list of channels for your app, nothing else
but a broken NotificationChannel setup could possibly have trickle-down effects elsewhere
so, I'd remove the setGroup() call from your NotificationChannel.Builder code
Mathias
allright. ok i'll start there i guess
Mark M.
also, you might try a full uninstall/reinstall of the app, as Android gets weird when you try changing NotificationChannels
4:35 PM
Mathias
ok thanks. ill try, and if i can't get it to work i'll hang around here again next time :)
Mark M.
OK
let me take a question from Aaron, and I'll return to you in a bit
Aaron: your turn! do you have a question?
Aaron
I have 8 questions today, lol. I'll just paste them all in at once and you can answer what you are able to
View paste (6 more lines)
* In logcat, I sometimes see a dollar sign and number following a fragment name. E.g., Repository$2.  What does this mean? That the class is an inner class? A 2nd, 3rd, instance of the class? I could only find a couple discussions about this on Google.

* In an app architecture where your View observes LiveData in your ViewModel, and your ViewModel "observes" a repository with a switchMap transformation, how and why does this get around the restriction on ViewModels not observing LiveData? What happens differently under the hood that makes this OK, but a regular observer not OK?

* In a MVVM app with repository, should the repository generally not know the ViewModel? I'm using this pattern in my app, which is why I am using switchMap the way I just described. I could avoid using switchMap if the repository knows the ViewModel - not sure which way is best - any comments?

* When a LiveData observer is registered, it is tied to the specific object that the reference variable "points" to at the time, right? Meaning, if the reference variable later changes, your observer stays tied to the original object? 

* Either your book or the official Android docs says something like, "what make
...
Mark M.
oboy
"What does this mean? That the class is an inner class?" -- yes, where the number identifies the specific inner class (though I'm not sure if they are assigned top-down or what)
Aaron
Haha
Mark M.
"how and why does this get around the restriction on ViewModels not observing LiveData?" -- um, because it is not observing the LiveData. It does not have its own Observer. It is basically chaining two LiveData objects together, and the activity/fragment is observing the pair.
"should the repository generally not know the ViewModel?" -- yes
"I could avoid using switchMap if the repository knows the ViewModel - not sure which way is best - any comments?" -- well, personally, I tend to use RxJava and make the changeover to LiveData last via LiveDataReactiveStreams
but a repository definitely should not know anything about viewmodels IMHO
4:40 PM
Aaron
OK. Planning to tackle RxJava after I get the first version of this app published
Mark M.
"When a LiveData observer is registered, it is tied to the specific object that the reference variable "points" to at the time, right?" -- I do not know what the "reference variable" is
Aaron
incorrect term on my part
Mark M.
"I don't really get why this prevents the [static] object from being garbage collected when it goes out of scope." -- a static object never goes out of scope
"what is the best way to analyze my transitive dependencies to make sure I'm not pulling in any superfluous artifacts in my build.gradle file?" -- right now, examine the External Libraries roster in the project tree, and see if anything scares you
I have toyed with trying to create some sort of tool to help with this
"instead of putting component code in your fragment's lifecycle methods, you should define an interface on the fragment and have your activity implement it, to make your fragment reusable elsewhere" -- I do not know what "component code" is. The contract pattern is a way for a fragment to talk to a hosting activity without caring the actual class of that activity. This is useful when a fragment might be hosted by more than one activity.
"Is the benefit here primarily avoiding memory leaks and crashes that would result from calling methods in the component when it's in the wrong lifecycle state?" -- that's a reasonable summary
I think I hit all 8
Aaron
yes, thanks!
Mark M.
digest those answers for a couple of minutes, and I'll be back to you shortly
Aaron
to clarify to the question about LiveData
4:45 PM
Mark M.
Mathias: do you have another question?
Mathias: if you come up with another question, chime in
Aaron: in the meantime, back to you!
Aaron
sure thing, feel free to get back with Mathias. What I meant to say is, if you start observing a LiveData and then you reassign that LiveData to wrap a different data source (with the = operator, instead of calling setValue or postValue), you will break your code, right?
disregard first sentence
Mark M.
um, do you mean that you have private SomethingLiveData foo=new SomethingLiveData(), have an activity/fragment start observing that, then have foo=new SomethingLiveData() later?
Aaron
yes
Mark M.
well, the activity/fragment won't somehow know about that change, and it will be continuing to observe the other LiveData
Aaron
yes, ok, that's what I was asking. great
Mark M.
generally, the LiveData that your activity/fragment subscribes to should be stable for the logical lifetime of the activity/fragment (i.e., surviving configuration changes, etc.)
4:50 PM
Mark M.
you will often see them marked as final in a ViewModel, for example
Aaron
yeah, I have noticed that. makes perfect sense now
thanks for correcting my terminology and reformulating my questions! that often helps me correct my mental models of this stuff - often times responses on stack overflow tend to be pedantic and guys on there like to answer questions in rigid ways, instead of trying to help reformulate the question to the one the poster meant to ask - one of the things that has made this chat very helpful
I have one other thing to add, as a follow up to a previous chat
Mark M.
OK
Aaron
I had mentioned previously that in the app I am working on (reddit client) the base URL is different for the OAuth API and the main content API
Mark M.
yes, I recall that
Aaron
and the solution you offered at the time was to have a second Retrofit instance
Mark M.
yes, perhaps tied to a shared OkHttpClient instance
Aaron
yeah
4:55 PM
Mark M.
so... how did it work out?
Aaron
I found another solution, using dynamic URLs with the @Url annotation - this let me consolidate it all into one retrofit instance. I assume you were already aware of this, maybe I just asked the question wrong, but I thought I would let you know how I solved it, which seems preferable to having 2, even tied together with the same OkHttpClient
Mark M.
I'm not familiar with that annotation -- hold on...
Aaron
and if that is bad for some reason, interested in your commentary
Mark M.
so, both URLs had the same host?
Aaron
one is oauth.reddit.com, the other is www.reddit.com
Mark M.
OK, I'll chalk this one up as a ¯\_(ツ)_/¯
that annotation is not well-documented
but, if it is working for you, it's probably fine
Aaron
sounds good
thanks for answering all my questions, and have a pleasant weekend!
Mark M.
you too!
Mathias: any last questions?
Aaron
has left the room
5:00 PM
Mark M.
OK, that's a wrap for today's chat
the transcript will go up on https://commonsware.com/office-hours/ shortly
the next chat is Tuesday at 7:30pm US Eastern
have a pleasant day!
Mathias
has left the room
Mark M.
turned off guest access

Tuesday, August 14

 

Office Hours

People in this transcript

  • Aaron
  • Mark Murphy
  • Mathias