Aug 18 | 3:50 PM |
Mark M. | has entered the room |
Mark M. | turned off guest access |
Mark M. | turned on guest access |
Aug 18 | 3:55 PM |
Mathias | has entered the room |
Mathias |
Hi Mark, hows it going
|
Mark M. |
hello, Mathias!
|
Mark M. |
OK, and you?
|
Mathias |
Of all good. little rainy here in Stockholm
|
Mathias |
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
|
Aug 18 | 4:00 PM |
Mathias |
got most stuff sorted, but have two things that i'm working on
|
Mathias |
i prepared a long(ish) text for some background, i'll paste it
|
Mathias |
it's about geofences and the background restrictions in Oreo
|
Mathias |
View paste
|
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
|
Mark M. |
you can use that, as well as you used the regular service, so long as it is a foreground service and you use startForegroundService()
|
Mark M. |
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
|
Mark M. |
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?
|
Mark M. |
do you supply a PendingIntent or something?
|
Aug 18 | 4:05 PM |
Mathias |
yeah. you register the geofence like this:
|
Mathias |
View paste
|
Mathias |
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
|
Mathias |
"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
|
Aug 18 | 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
|
Mark M. |
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
|
Mathias |
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
|
Mark M. |
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
|
Mathias |
sorry, im writing explanation
|
Mark M. |
I thought you were naming an Android version :-)
|
Aug 18 | 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
|
Mark M. |
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
|
Mark M. |
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?
|
Mathias |
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
|
Mathias |
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
|
Mathias |
2. make a server call when location comes back
|
Mathias |
but in the context of a geofence trigger, what IS "foreground service"? we're already in the background?
|
Aug 18 | 4:20 PM |
Mark M. |
a foreground service is a service in which you call startForeground()
|
Mark M. |
passing in a Notification
|
Mathias |
ok
|
Mark M. |
now, I have not played with geofences, let alone on Android 8.0+
|
Mark M. |
according to https://developer.android.com/about/versions/or..., you should still get relatively frequent geofence triggers
|
Mark M. |
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
|
Mathias |
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
|
Mathias |
so, next topic. Notifications!
|
Aug 18 | 4:25 PM |
Mathias |
tldr: i have grouped/stacked notifications. They have stopped working in sdkLevel 26.
|
Mark M. | |
Mark M. |
is that yours
|
Mark M. |
?
|
Mathias |
:)
|
Mathias |
You're faster than Lucky Luke
|
Mark M. |
I remembered seeing the question
|
Mark M. |
I do not know what is going on there
|
Mark M. |
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
|
Mark M. |
you seem to be reusing CHANNEL_ID_EVENTS
|
Mark M. |
that is the name of the NotificationChannel... and of a NotificationChannelGroup?
|
Mark M. |
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?
|
Aug 18 | 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
|
Mark M. |
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
|
Mathias |
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)
|
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
|
Mark M. |
but a broken NotificationChannel setup could possibly have trickle-down effects elsewhere
|
Mark M. |
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
|
Aug 18 | 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
|
Mark M. |
let me take a question from Aaron, and I'll return to you in a bit
|
Mark M. |
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
|
Aaron |
View paste
(6 more lines)
|
Mark M. |
oboy
|
Mark M. |
"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.
|
Mark M. |
"should the repository generally not know the ViewModel?" -- yes
|
Mark M. |
"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
|
Mark M. |
but a repository definitely should not know anything about viewmodels IMHO
|
Aug 18 | 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
|
Mark M. |
"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
|
Mark M. |
I have toyed with trying to create some sort of tool to help with this
|
Mark M. |
"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.
|
Mark M. |
"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
|
Mark M. |
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
|
Aug 18 | 4:45 PM |
Mark M. |
Mathias: do you have another question?
|
Mark M. |
Mathias: if you come up with another question, chime in
|
Mark M. |
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?
|
Aaron |
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.)
|
Aug 18 | 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
|
Aaron |
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
|
Aaron |
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
|
Aug 18 | 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 ¯\_(ツ)_/¯
|
Mark M. |
that annotation is not well-documented
|
Mark M. |
but, if it is working for you, it's probably fine
|
Aaron |
sounds good
|
Aaron |
thanks for answering all my questions, and have a pleasant weekend!
|
Mark M. |
you too!
|
Mark M. |
Mathias: any last questions?
|
Aaron | has left the room |
Aug 18 | 5:00 PM |
Mark M. |
OK, that's a wrap for today's chat
|
Mark M. |
the transcript will go up on https://commonsware.com/office-hours/ shortly
|
Mark M. |
the next chat is Tuesday at 7:30pm US Eastern
|
Mark M. |
have a pleasant day!
|
Mathias | has left the room |
Mark M. | turned off guest access |