Office Hours — Today, March 17

Saturday, March 14

Mar 17
7:20 PM
Mark M.
has entered the room
Mark M.
turned on guest access
7:30 PM
Jan S.
has entered the room
Mark M.
hello, Jan!
how can I help you today?
7:35 PM
sudokai
has entered the room
sudokai
Hi
Mark M.
hello, sudokai!
Jan S.
View paste
I've been comparing your way of doing things to some other examples for RoomDatabase.  One thing I notices was this type of code:  single { BookDatabase.newInstance(androidContext()) }
I'm used to seeing a lock or something put around the databaseBuilder because you need to make sure of only one instance.  Is your way better?
Mark M.
(sudokai: let me take a question from Jan, and I'll be with you shortly!)
Jan: that sample is using Koin for dependency inversin
er, dependency inversion
it is Koin's responsibility to ensure that a single is truly a singleton and does not wind up creating multiple instances
sudokai
Yes, no problem. Other people's questions can be interesting as well.
Mark M.
so, whatever locking exists (and there should be some) would be inside of Koin, which is why you don't see it in the app code
the same thing holds true for other DI frameworks, like Dagger -- you rely on the DI container to handle thread safety for initializing singletons
if you are not using DI, though, you probably do want to have some sort of synchronization on creating your own singletons
Jan S.
Okay. I had the FTS project open and did a search and that is what it gave me.
Mark M.
yes, most of the examples in *Elements of Android Room* will be using Koin for DI
Koin is lightweight, with an easy API, making it great for book examples :-)
I have more material on Koin for DI in *Elements of Android Jetpack*
7:40 PM
Mark M.
let me take a question from sudokai, and I'll swing back to you in a bit if you have another question or follow-up from this one
7:40 PM
Mark M.
sudokai: your turn! how can I help you today?
sudokai
I was wondering about your views on using fragments vs custom views.
Mark M.
generally, I try to avoid custom views, as I find them to be aggravating
so, in a case where those solutions are equally possible, I would use a fragment
sudokai
So far, I have seen that's not straight forward to access the viewModel from a custom view
Mark M.
ideally, IMHO, a custom view knows nothing about viewmodels, lifecycles, or any of that stuff
it just draws the UI and provides basic processing for user input
the less code you have in a custom view, the better
sudokai
What would you use custom views for then?
Mark M.
only for cases where I needed to subclass an existing view to tweak functionality, particularly where it involves protected methods
also, possibly, for cases where I need it to go places where a fragment can't go very easily (e.g., rows in a RecyclerView)
7:45 PM
sudokai
I've inherited a project that has a custom view that's the upload status bar of an item detail.
Mark M.
what is it customizing?
sudokai
The item detail is a fragment, and it passes the model to the custom view. Then the custom view checks the model's properties and changes dynamically. It also exposes an "retry upload" button that's conditionally shown depending on the state of the model.
When you tap on the button, the custom view itself launches the upload through a JobScheduler
Mark M.
um, to be completely honest: that sounds awful
sudokai
Would you use a fragment for this instead? It seems like things are a bit coupled.
Mark M.
I'd start by getting that upload logic out of a view and into a viewmodel or possibly a repository
is this being reused anywhere else? if not, and if there is no custom onDraw() or similar rendering logic, I'd just try to flatten it all into the fragment that is hosting it
7:50 PM
sudokai
I don't think it's being used anywhere else. The custom view is extending framelayout and the layout file starts with <merge>
Inside there's a linearlayout
Mark M.
my guess is that you could move that stuff into the fragment's layout and the rest of the logic have split between the fragment and a viewmodel, but that is just a guess, given that I can't exactly see your code from here :-)
let me take another question from Jan, and I'll swing back to you in a bit
Jan: your turn! do you have another question?
Jan S.
If you started out with an activity and now need a viewModel, should you change to use a fragment+viewModel? Or try to keep with activity and just add the viewModel?
Mark M.
there is nothing wrong with using a viewmodel with an activity
many of my samples do that, if I do not have a particular reason to need a fragment
Jan S.
I'm using recyclerview and adapter with activity if that makes a difference.
Mark M.
Google's stated preference is for fewer activities and more fragments, so it is possible that you might introduce a fragment as part of a wholesale shuffling of your architecture
7:55 PM
Mark M.
but, looking at a single activity in isolation, unless you are embarking on a "put everything in fragments" policy with an eye towards long-term conversion, do not feel that you have to force in a fragment without a reason
Jan S.
I use a fragment on the detail view just not its "master".
Mark M.
there, it is possible that you might want to have a fragment as part of supporting larger screens, such as tablets
the master-detail pattern was the classic reason for introducing fragments in the first place, after all
however, you may have other plans for larger screens
(such as "we'll worry about them sometime later")
so, add a fragment if the fragment adds value
if you do not perceive near-term value and aren't sure about long-term value, don't worry about it
let me take another question from sudokai, and I'll be back with you in a bit
Jan S.
Okay. Just wanted to do it now if there was an obvious need based on using ViewModel. So I should be okay.
Mark M.
yes, you should be fine!
sudokai: your turn! do you have another question?
sudokai
Yes
I asked this a few days ago in the forum but I'm still dealing with the issue
8:00 PM
sudokai
I had some singletons in my Application class that are then used everywhere in the app
The singletons are initialized in onCreate
Mark M.
sudokai
Yes!
Mark M.
OK, just making sure I had the background
sudokai
So, I'm starting to move stuff to Koin, but the app is huge and in the mean time, I moved some of the singletons to a external class outside of Application
I called it SharedContext
So inside I have
val contextRef: AtomicReference<Context> = AtomicReference()
View paste
val sharedPreferences: SharedPreferences by lazy {
    contextRef.get().getSharedPreferences("prefs", Activity.MODE_PRIVATE)
  }
Then the first thing I do in the JobService or the Application class is to set the contextRef with the context value if it's null
This is done in a synchronized function with double null check
But!!
In my Google Play Store ANR/Crashes pages, I'm still seeing crashes inside Activites
When they try to access SharedContext.sharedPreferences
How is that possible??
Mark M.
what is the exception?
sudokai
The order of creation should be Application oncreate, then Activity onCreate
Mark M.
yes
8:05 PM
sudokai
Give me a sec, if you want to switch to Jan
I'll look for the exception
Mark M.
OK, let me know when you're ready
in the meantime... Jan: do you have another question?
Jan S.
No. Not right now.
Mark M.
OK -- if you think of one, let me know!
sudokai
It's an NPE!
Mark M.
do you have the stack trace? is it directly on the getSharedPreferences() call, or is it somewhere deeper?
sudokai
View paste (8 more lines)
Caused by: java.lang.NullPointerException: 
  at com.app.appandroid.SharedContext$sharedPreferences$2.invoke (SharedContext.java:30)
  at com.app.appandroid.SharedContext$sharedPreferences$2.invoke (SharedContext.java:13)
  at kotlin.SynchronizedLazyImpl.getValue (SynchronizedLazyImpl.java:74)
  at com.app.appandroid.SharedContext.getSharedPreferences (SharedContext.java:7)
  at com.app.appandroid.utils.PreferenceFlagsUtils.isFlagTrue (PreferenceFlagsUtils.java:89)
  at com.app.appandroid.utils.PreferenceFlagsUtils.isFirstInstall(PreferenceFlagsUtils.java:28)
  at com.app.appandroid.view.activities.SplashActivity.onCreate (SplashActivity.java:54)
  at android.app.Activity.performCreate (Activity.java:7327)
  at android.app.Activity.performCreate (Activity.java:7318)
  at android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1275)
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3101)
  at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:3264)
  at android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:78)
  at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecu
...
8:10 PM
sudokai
It only happened to 1 person, but 13 times
I wonder if it's an Android bug
Mark M.
and line 30 is the contextRef.get().getSharedPreferences("prefs", Activity.MODE_PRIVATE) line?
sudokai
Yes, exactly
Mark M.
what is the make and model of this user's device? some device manufacturers... let's just say that "quality assurance" isn't just a phrase, but rather a long-term objective
sudokai
Galaxy A40
Mark M.
well, that's pretty conventional
sudokai
Not the most high end Samsung
I've pasting a bit more of context
View paste
object SharedContext {

  val contextRef: AtomicReference<Context> = AtomicReference()

  val sharedPreferences: SharedPreferences by lazy {
    contextRef.get().getSharedPreferences("prefs", Activity.MODE_PRIVATE)
  }
Mark M.
I assume that SplashActivity is a splash screen and has your MAIN/LAUNCHER <intent-filter>
sudokai
Yes
View paste
public static synchronized void initSingletons(Context context) {
    if (SharedContextSharedContext.INSTANCE.getContextRef().get() == null) {

        SharedContextSharedContext.INSTANCE.getContextRef().set(context);
That's the inner null check
Outside of that there's a outer null check:
Mark M.
that seems like overkill
at least for this case
just call set(context) and be done with the matter
8:15 PM
sudokai
View paste
if (SharedContext.contextRef.get() == null) {
    App.initBackgroundServices(applicationContext)
}
Mark M.
even if for some completely bizarre reason, get() returned a non-null value... do you care?
sudokai
Well, I'm seeing ghosts everywhere, so I don't want to risk a double initialization lol
Sorry, switched names
Mark M.
well, at least for this case, it's not really an initialization -- you're just populating this AtomicReference
sudokai
Yeah, well, there's more stuff afterwards, like Crashlytics and broadcast receivers and whatnot
Mark M.
but that too should just be stuff that you have in onCreate() of the Application subclass
what you have would not explain the crash, but it adds complexity and clutter that might be hiding something else that would explain the crash
frankly, if it is just one user, for a weird case like this, I tend to assume it is somebody reverse-engineering the app or something
and they are monkeying around with the app initialization
sudokai
Okay, sounds fair.
I've been pulling my hair out.
Mark M.
I don't really recommend that
8:20 PM
Mark M.
(speaking as somebody who can practically count the individual hairs left on my head)
so, I'd try to streamline your initialization logic, if possible, along with your migration to Koin
but, to get to your question: there is no scenario in which onCreate() of an Activity should be called where onCreate() of the corresponding Application would not have been called
sudokai
Yeah, this is basically a stopgap measure. I pulled the logic out of Application into a ShareContext so that my JobService could initialize it
Mark M.
which also shouldn't be needed, insofar as I would expect onCreate() of the Application to be invoked before your JobService gets to run its job
there is a very short list of things that happen before onCreate() of the Application
1. onAttach() on the Application
2. constructors for any ContentProvider implementations
and AFAIK, that's it
sudokai
Okay, make sense. Thanks.
Can I stil ask another question?
Mark M.
Jan: do you have another question, or can sudokai continue?
8:25 PM
Jan S.
I have 2 things. First page 149 of Room book. Last sentence. Did you mean unnecessarily? Didn't read all context so wasn't sure.
Mark M.
no, I think I have that right
Jan S.
Second: article on medium says " Note: Room uses its own dispatcher to run queries on a background thread. Your code should not use withContext(Dispatchers.IO) to call suspending room queries. It will complicate the code and make your queries run slower." Your bookRepository import function uses it though. So confused.
Mark M.
the import() function is doing more than Room I/O, though
I am reading in assets, in particular, which is I/O
when I make the insert() call, Room will switch the CoroutineContext to one that uses its own dispatcher
Jan S.
okay. nevermind then. Just saw the brackets enclosed only that.
Mark M.
if I were creating the entities purely from memory, I could skip my own withContext() call -- it's the I/O that triggers my need for withContext(Dispatchers.IO), even though Room will use its own dispatcher for its portion of the import() work
Jan S.
That's all I have for now. Thanks.
Mark M.
OK
sudokai: we're almost out of time -- got something quick?
sudokai
Yes
If you launch multiple Workrequests in parallel and chain another one after it
8:30 PM
sudokai
Then you replace one of the parallel workrequests, the chained workrequest gets cancelled as well?
Mark M.
I have no idea, sorry
I haven't tried doing that
sudokai
Okay, I will try it and see what happens
Mark M.
my WorkManager use cases have been very simple
sudokai
Will let you know
Mark M.
sounds good!
and, that's a wrap for today's chat
the transcript will go up on https://commonsware.com/office-hours/ shortly
sudokai
Thanks Mark!
Mark M.
the next chat is Thursday at 8:30am US Eastern
Jan S.
Thank you so much for your time.
Mark M.
thanks for attending, have a pleasant evening, and stay healthy!
sudokai
Stay healthy!
Jan S.
has left the room
sudokai
has left the room
Mark M.
turned off guest access

Saturday, March 14

 

Office Hours

People in this transcript

  • Jan Smith
  • Mark Murphy
  • sudokai