0

Im working on an app, which has a Fragment containing a RecyclerView, which looks for saved songs data using a cursor. Whenever i open the fragment it takes some milliseconds to load. The Verbose infact flags the skipped frames:

Skipped 33 frames! The application may be doing too much work on its main thread.

What can i do to fix this? Where can i learn more about this?

Heres my fragment.kt code:

class FragmentTrack : Fragment() {
    var trackList = mutableListOf<DataItems>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? = inflater.inflate(R.layout.fragment_track, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        var songCursor : Cursor? = activity?.contentResolver?.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            null, null, null, null)

        while (songCursor != null && songCursor.moveToNext()) {
            var songName = songCursor.getString(songCursor.getColumnIndex(MediaStore.Audio.Media.TITLE))
            var songArtist = songCursor.getString(songCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST))

            trackList.add(DataItems(songName, songArtist))
        }

        rvTracks.apply {
            setHasFixedSize(true)
            layoutManager = LinearLayoutManager(activity)
            adapter = AdapterList(trackList)
        }

        topToolbar.inflateMenu(R.menu.menu_toolbar)

        topToolbar.setNavigationOnClickListener {
            startActivity(Intent(activity, ActivitySearch::class.java))
        }

        topToolbar.setOnMenuItemClickListener {
            when(it.itemId) {
                R.id.fsvSettings -> this.startActivity(Intent(activity, ActivitySettings::class.java))
            }
            true
        }
    }
}
Meltix
  • 436
  • 6
  • 22
  • 1
    Does this answer your question? [The application may be doing too much work on its main thread](https://stackoverflow.com/questions/14678593/the-application-may-be-doing-too-much-work-on-its-main-thread) – Abhimanyu Nov 01 '20 at 12:33

1 Answers1

1

Looking at your code, the potential candidate to blame seems to the database query + iteration, so the next section:

class FragmentTrack : Fragment() {

    ....

    var songCursor : Cursor? = activity?.contentResolver?.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        null, null, null, null)

    while (songCursor != null && songCursor.moveToNext()) {
        var songName = songCursor.getString(songCursor.getColumnIndex(MediaStore.Audio.Media.TITLE))
        var songArtist = songCursor.getString(songCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST))

        trackList.add(DataItems(songName, songArtist))
    }

    ....
}

To avoid blocking the UI, all potentially heavy operations (such as Database Access, IO, Networking), should be performed in a background thread or coroutine.

Try querying + loading the data asynchronously and update the adapter once it's ready.

Update answering your comments. Some links to get you started:

https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb

https://developer.android.com/kotlin/coroutines

https://developer.android.com/kotlin/coroutines-adv

https://developer.android.com/guide/background/threading

https://developer.android.com/guide/background

PerracoLabs
  • 16,449
  • 15
  • 74
  • 127
  • Thanks for your answer! Are there any docs i can read to learn more about Async and Tasks? Im new to this stuff – Meltix Nov 01 '20 at 12:54
  • 1
    I've added at the bottom of the answer some links to get you started. – PerracoLabs Nov 01 '20 at 13:15
  • 1
    @Meltix you should read about android room database which is part of android jetpack components. Here is docs about it https://developer.android.com/training/data-storage/room – TRose Nov 01 '20 at 13:46
  • @Lirus sure! Thank you too! – Meltix Nov 01 '20 at 14:09
  • @PerracoLabs Hey! I tried implementing a corountine for this, but it doesnt really work, i still get 30 frames skipped :/ Here's what i did: Fragment - https://pastebin.com/CsMyktD0 ViewModel - https://pastebin.com/B6gJ4iAT Did i missed something? – Meltix Nov 02 '20 at 15:01
  • @Meltix In your code the query is still performed in the UI thread. All database operations including getting a cursor should be asynchronous, not only the cursor iteration. – PerracoLabs Nov 02 '20 at 15:11
  • @PerracoLabs Okay! Moved the cursor right now, removed "activity?" from the cursor declaration and now contentResolver is flagged as unresolved reference... what should i do? I have updated the ViewModel pastebin: https://pastebin.com/B6gJ4iAT – Meltix Nov 02 '20 at 15:36
  • 1
    @Meltix In the coroutine you are calling the ContentResolver from nowhere, this cannot work and will fail as it can't resolve the reference. A ContentResolver can be obtained only via a context and consequently from an Activity. What you can do is to pass the ContentResolver as a parameter to your coroutine, this has no negative impact, as the point is to ensure the "query" call is executed within the coroutine. So besides passing the trackList parameter, also pass the ContentResolver. – PerracoLabs Nov 02 '20 at 20:14
  • @PerracoLabs followed your suggestion and we might be closer. I passed as parameter ContentResolver and i declared the Cursor inside the coroutine. Now i get the error: "java.lang.IllegalStateException: Couldn't read row 0, col -1 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it." I've updated both pastebins, check there https://pastebin.com/CsMyktD0 https://pastebin.com/B6gJ4iAT – Meltix Nov 02 '20 at 20:37
  • Check the next official sample. Although is for images the logic is similar. You will want to check specifically the "queryImages" method. Pay attention to the comments to understand why does everything: https://github.com/android/storage-samples/blob/master/MediaStore/app/src/main/java/com/android/samples/mediastore/MainActivityViewModel.kt – PerracoLabs Nov 02 '20 at 21:07
  • @PerracoLabs theres good news and two bad news. Good news is, that playing around with the source you gave me works. Bad news number 1, with this system, i can only get songs's ID, but not title, throwing me an error. Bad news number 2, the application still suffers of UI block, making all of these steps useless... I have updated the ViewModel: https://pastebin.com/B6gJ4iAT – Meltix Nov 02 '20 at 21:49
  • 1
    @Meltix Regardless if the skipped frames may be produced by something else, calling the ContentResolver async is a must be done action. Besides the documentation as you can see in the official sample even google states that working with ContentResover is slow and must be done outside the main thread. If this part is now working correctly asynchronously, then try to pin-point other factors, for example, is your layout very complex? If yes, then try to load it asynchronously with "AsyncLayoutInflater" https://developer.android.com/reference/androidx/asynclayoutinflater/view/AsyncLayoutInflater – PerracoLabs Nov 02 '20 at 22:23
  • Actually now that i notice it is definetly faster than before. One last thing, is it possible to fetch the Song's name using its ID? Apparently i cant fetch the indexcolumn of Title since it doesnt exist – Meltix Nov 02 '20 at 22:41
  • @Meltix Do you mean it fails if you try to get the index of the column? – PerracoLabs Nov 02 '20 at 23:31
  • @PerracoLabs yes, exactly – Meltix Nov 03 '20 at 05:13
  • This would be a completely different issue, I would recommend to post a different question to not mix concepts. – PerracoLabs Nov 03 '20 at 09:21