Apr 4 | 7:20 PM |
Mark M. | has entered the room |
Apr 4 | 7:25 PM |
Mark M. | turned on guest access |
Paul R. | has entered the room |
Paul R. |
Greetings again.
|
Mark M. |
hello!
|
Mark M. |
how can I help you today?
|
Paul R. |
I have two questions for you tonight. First up is performance. On my phat, fully loaded MacBook Pro a kotlin program I wrote takes 2 seconds to load ~2Million JSON records from a web site.
|
Mark M. |
I'm impressed you can download a JSON document that large in 2 seconds
|
Paul R. |
When I run that code via a library on an Android app on a Pixel 3 XL it takes ~15 seconds, and on a Nexus 6, fully two minutes.
|
Paul R. |
The disparate times seem out of whack to me. I would have guessed something like, 2s, 5s, 20s would be reasonable. Apparently not the case. What's your take?
|
Apr 4 | 7:30 PM |
Mark M. |
when you say "load", what exactly do you mean? you're going to be hard-pressed to have heap space for ~2 million model objects
|
Paul R. |
Why is that?
|
Mark M. |
um, because heaps aren't that big
|
Mark M. |
say, for example, you have a 64MB heap
|
Mark M. |
that leaves you with 32 bytes per model object
|
Mark M. |
while it is certainly possible to have a model object that small that is still useful, frequently JSON will have string properties and stuff that will bump your average up
|
Paul R. |
With a 3G Android device, can I not set the heap to, say, 1G?
|
Mark M. |
so, if you really are loading 2 million model objects into memory simultaneously, IMHO slow speed is the least of your worries -- the OutOfMemoryErrors would be somewhat more catastrophic
|
Mark M. |
no
|
Mark M. |
unless you build your own custom ROM
|
Mark M. |
or root the device, perhaps
|
Paul R. |
I was getting OOM errors until I used the "useLargeHeap" attribute.
|
Mark M. |
there is no guarantee of how large that "large heap" will be
|
Paul R. |
Then it garbage collected like crazy on the N6.
|
Mark M. |
it is probably trying to free up memory from past JSON parsing steps or something
|
Paul R. |
So what would you do to make these ~2M records accessible fast and easy?
|
Mark M. |
eliminate 99% of them
|
Apr 4 | 7:35 PM |
Mark M. |
I mean, seriously, downloading and parsing 100+MB is not going to be pleasant
|
Mark M. |
and so I'd start with reconsidering why you are downloading and parsing 100+MB of JSON data
|
Paul R. |
These are records that back narrowing choices, like movie names in my app. Can't be eliminated.
|
Mark M. |
but, why download and parse JSON? why not package this data in your app as a SQLite database?
|
Paul R. |
I hate databases. :-)
|
Paul R. |
Especially SQL ones.
|
Paul R. |
You've got me thinking. I might load the strings and defer parsing.
|
Paul R. |
They are small, always < 100 characters
|
Mark M. |
OK, if you're not putting the data into SQLite... then what *are* you doing with the data? please tell me you are not re-parsing the JSON on every app run
|
Apr 4 | 7:40 PM |
Paul R. |
If I could get the re-parsing down to < 2s on every app run (which are infrequent) I would accept that.
|
Mark M. |
neither the network nor on-board flash is likely to be fast enough to sustain what you want
|
Paul R. |
But yes, cacheing the data into a db is viable.
|
Paul R. |
The data changes once per day at the source site, fwiw.
|
Eric | has entered the room |
Mark M. |
how big is your intended user base?
|
Mark M. |
(BTW, hello, Eric -- I will be with you shortly!)
|
Eric |
hey
|
Paul R. |
Could be large.
|
Paul R. |
I certainly want it to be large.
|
Mark M. |
downloading 100+MB daily is going to make you unpopular with people on metered data plans
|
Mark M. |
and I have difficulty believing that the entire dataset is being changed daily
|
Mark M. |
I mean, you could rewrite half your app in C/C++, so that your JSON is only ever touched by native code, as you eliminate the heap limits... but I think you have far more profound data management issues than the speed of the JSON parsing
|
Paul R. |
It's an upscale user base. Not for billions of users. Such a problem to have.
|
Apr 4 | 7:45 PM |
Paul R. |
Are these profound issues speed or space or both?
|
Mark M. |
bandwidth and speed, IMHO
|
Mark M. |
I mean, you're talking terabytes a month of bandwidth charges if your user base gets appreciable
|
Mark M. |
at ~3GB/month per user
|
Mark M. |
well, actually, I'm not taking GZip into account, which I assume you have enabled for your Web service and HTTPS client
|
Mark M. |
so, the bandwidth hopefully won't be that bad
|
Mark M. |
but, still, the thought of downloading and parsing that much JSON daily makes me cringe
|
Paul R. |
Interestingly enough, with Kotlin coroutines, the delta between reading in the data from a file or from the next is negligible.
|
Paul R. |
(reading and parsing)
|
Paul R. |
next -> net
|
Mark M. |
that will depend a lot on the network connection
|
Paul R. |
Yes, mine is fast (1G)
|
Mark M. |
anyway, let me take a question from Eric, and I will swing back to you in a bit
|
Mark M. |
Eric: your turn! do you have a question?
|
Paul R. |
That's fair.
|
Eric |
I have an activity with a framelayout that acts as a container for dynamically added fragments. One such fragment has swipeable tabs, so it contains 2 fragments itself. When a menuItem is clicked, I want to add or remove these inner fragments. I am using the childFragmentManager to do so and get an error complaining "can't change container ID of <inner fragment>". Since these inner fragments were added with a FragmentPagerAdapter I didn't use the childFragmentManager directly to add them.
|
Mark M. |
FragmentPagerAdapter has always sucked at dealing with trying to add/remove/replace pages
|
Apr 4 | 7:50 PM |
Mark M. |
that's why I wrote ArrayPagerAdapter back in the day
|
Mark M. |
you may need to roll your own PagerAdapter that can handle your needs
|
Eric |
yeah I created a custom FragmentPagerAdapter
|
Mark M. |
OK, well, that adapter needs to be using the childFragmentManager, if it will manage fragments that are inside another fragment
|
Mark M. |
beyond that, based on the error, it seems like your FragmentTransaction has some container issues
|
Eric |
yes it is using that
|
Eric |
am I able to use the same framelayout container?
|
Mark M. |
I thought you were changing a page in the ViewPager
|
Eric |
no
|
Mark M. |
"When a menuItem is clicked, I want to add or remove these inner fragments" -- what are the inner fragments?
|
Mark M. |
I thought those were the ones referred to as "2 fragments itself" in the preceding sentence
|
Eric |
when I click on a menuItem in the toolbar, I want to go from showing 2 tabs to one of the fragments in the tab (I want to replace the parent fragment with the child fragment)
|
Apr 4 | 7:55 PM |
Eric |
the parent fragment is the tab fragment
|
Eric |
the child fragment is one of two in the parent tab fragment
|
Mark M. |
"I want to replace the parent fragment with the child fragment" -- you would need to completely remove the child fragment before adding it to a new container, I think
|
Mark M. |
I have never tried what you are attempting (move a nested fragment out to be a top-level fragment), but once a fragment is managed, AFAIK you need to unmanage it before you can switch containers
|
Eric |
am I able to use the framelayout that is currently occupied by my parent fragment? This container is in an activity. It holds a tab fragment. The tab fragment has child fragments. So I want to replace the tab fragment with its child. Do you understand
|
Mark M. |
in theory, what you want can work -- it is mostly a question of what fragments you need to remove along the way
|
Mark M. |
I would start by removing *everything* -- remove the child fragments from the tab fragment, remove the tab fragment from the FrameLayout
|
Mark M. |
then add the desired fragment to the FrameLayout
|
Mark M. |
and see if that runs
|
Eric |
the stacktrace seems to indicate I can't use the same container
|
Apr 4 | 8:00 PM |
Mark M. |
from the error message you cited, my assumption is that the fragment is already *in* a container
|
Eric |
"can't change the container id of the child fragment"
|
Mark M. |
and you need to remove it from that container before putting into another container
|
Eric |
hmm alright
|
Mark M. |
it's entirely possible that there is something about nested fragments that prevents this sort of reuse, and you will wind up needing to create a new instance of the fragment
|
Mark M. |
that would be the fallback plan
|
Eric |
wait but container is it currently in?
|
Mark M. |
the container represented by the tab fragment
|
Eric |
the nested fragment is part of tabs, which does not explictly have a container, no?
|
Eric |
ah
|
Mark M. |
ViewPager is a container (subclass of FrameLayout IIRC)
|
Eric |
how do i remove it from the container, then?
|
Eric |
I don;t know how to acccess this special container?
|
Mark M. |
you remove it from the PagerAdapter, I think
|
Mark M. |
somewhere, your custom PagerAdapter should be committing FragmentTransactions for adding and removing the fragments
|
Mark M. |
ViewPager itself knows nothing about fragments -- it works in terms of Views
|
Mark M. |
let me take another question from Paul, and I will switch back to you in a little bit
|
Mark M. |
Paul: your turn! do you have another question?
|
Apr 4 | 8:05 PM |
Eric |
ok
|
Apr 4 | 8:05 PM |
Paul R. |
yes
|
Paul R. |
I have a Kotlin object that has an init block. I would like to "inject" a parameter into this init block for testing. First, is my scenario clear and second, do you have a solution?
|
Mark M. |
init blocks don't get parameters, only constructors do
|
Paul R. |
object as in singleton
|
Mark M. |
are you using a dependency injection framework?
|
Paul R. |
Can the block reference a companion variable?
|
Mark M. |
probably, though I haven't tried it
|
Paul R. |
Me either, at least not yet.
|
Mark M. |
companion objects are roughly analogous to Java statics, so I would think it would work
|
Mark M. |
but if you're not using dependency injection, that may be a better answer anyway
|
Mark M. |
personally, I've been using Koin for Kotlin projects
|
Paul R. |
I'll play with that idea. Any other thoughts?
|
Mark M. |
you could have some sort of secondary constructor that is used in testing
|
Mark M. |
(I am assuming that you cannot provide this parameter via the regular constructor for some reason)
|
Apr 4 | 8:10 PM |
Paul R. |
Would "secondary constructor kotlin" be a good search expression for Google, do you think?
|
Mark M. |
I cover it in "Elements of Kotlin", but yes, that should work from a search expression standpoint
|
Mark M. | |
Mark M. |
having full-text search of my books is handy on occasion :-)
|
Paul R. |
Ah, you are imaging an factory to create the object. No?
|
Paul R. |
an -> a
|
Mark M. |
I was assuming that your test was creating the object
|
Paul R. |
Not till now. :-)
|
Mark M. |
if this object is several layers removed from the test code... definitely look into Koin or other dependency injection, as that is precisely the problem that they were created to solve
|
Mark M. |
I'll be covering Koin some in *Elements of Android Jetpack* and *Exploring Android*, though my pace is slow at the moment
|
Mark M. |
BTW, the official docs for secondary constructors are part of https://kotlinlang.org/docs/reference/classes.html
|
Paul R. |
I'll take a look at Koin and ping you later if need be.
|
Mark M. |
I am eminently pingable
|
Mark M. |
except, not literally, due to my firewall configuration
|
Apr 4 | 8:15 PM |
Mark M. |
anyway, let me take another question from Eric, and I'll return to you if there is time
|
Mark M. |
Eric: back to you! do you have another question?
|
Paul R. |
A quick look tells me that is promising.
|
Paul R. |
I'm all set now. Thanks.
|
Mark M. |
you're welcome!
|
Paul R. | has left the room |
Eric |
yea it has a problem removing the inner fragment and gives the same error
|
Mark M. |
what is "it"? the PagerAdapter?
|
Eric |
childFragmentManager
|
Eric |
childFM.beginTransaction().remove(adapter.getItem(position)).commit()
|
Mark M. |
is the PagerAdapter using the childFragmentManager?
|
Eric |
yes
|
Mark M. |
then I cannot explain the error
|
Eric |
it has to because the fragments inside the tablayout are nested
|
Mark M. |
agreed
|
Eric |
ok
|
Mark M. |
"can't change the container id of the child fragment" simply does not make sense for a remove() operation
|
Mark M. |
are you also removing the fragment from the PagerAdapter? perhaps it is trying to re-add it as a page
|
Eric |
no
|
Apr 4 | 8:20 PM |
Mark M. |
you will need to do that, as you do not want the PagerAdapter to be working with this fragment anymore
|
Mark M. |
and the PagerAdapter should be doing the remove() as part of that work
|
Eric |
ok ill add a method to it that removes the fragment from the list
|
Eric |
goodnight
|
Mark M. |
have a pleasant evening!
|
Eric | has left the room |
Apr 4 | 8:25 PM |
Mark M. | turned off guest access |