0

So I have a bottom navigation setup with 5 fragments with Navigation Components. When the app launches the globalFragment skips 90+ frames when loading data into recycler view. and when I go to other fragment and press back button (which takes me to the globalFragment) it again skips frames(>100) but when I go to my last fragment which is a preference fragment and press back it's app hangs and skips 2500+ frames.

What I've tried

Removed data-binding from adapter seems to fix the initial frames skips when the app launches but when I navigate back from the settings fragment the app still skips 2500 frames

I removed the preference fragment and now the app doesn't hang but it still skips frames when coming back to the global fragment

I removed click listeners from the adapter and also the other unnecessary logic from onBind but the problem still persists

The project is open-sourced so you can check it out here https://github.com/destructo570/CovidTracker-kotlin

Here is my Logcat

2020-05-20 13:19:09.658 26583-26583/com.destructo.covidtracker 
W/to.covidtracke: Accessing hidden method 
Ljava/lang/invoke/MethodHandles$Lookup;-><init>(Ljava/lang/Class;I)V 
(greylist, reflection, allowed)
2020-05-20 13:19:09.675 26583-26583/com.destructo.covidtracker 
D/NetworkSecurityConfig: No Network Security Config specified, using platform 
default
2020-05-20 13:19:09.678 26583-26583/com.destructo.covidtracker 
W/to.covidtracke: Accessing hidden method Ldalvik/system/CloseGuard;- 
>get()Ldalvik/system/CloseGuard; (greylist,core-platform-api, reflection, 
allowed)
2020-05-20 13:19:09.678 26583-26583/com.destructo.covidtracker 
W/to.covidtracke: Accessing hidden method Ldalvik/system/CloseGuard;- 
>open(Ljava/lang/String;)V (greylist,core-platform-api, reflection, allowed)
2020-05-20 13:19:09.678 26583-26583/com.destructo.covidtracker 
W/to.covidtracke: Accessing hidden method Ldalvik/system/CloseGuard;- 
>warnIfOpen()V (greylist,core-platform-api, reflection, allowed)
2020-05-20 13:19:10.163 26583-26583/com.destructo.covidtracker 

I/Choreographer: Skipped 41 frames!  The application may be doing too much 
work on its main thread.
2020-05-20 13:19:10.168 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: doFrame is 686ms late because of 27 msg, msg 1 took 188ms 
(seq=38 running=106ms runnable=6ms late=134ms 
h=android.view.Choreographer$FrameHandler 
c=android.view.Choreographer$FrameDisplayEventReceiver), msg 24 took 451ms 
(seq=61 running=425ms runnable=2ms late=220ms h=android.os.Handler 
c=kotlinx.coroutines.DispatchedContinuation)
2020-05-20 13:19:13.541 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: Long Msg: seq=116 plan=13:19:12.044  late=1ms wall=1495ms 
running=1411ms runnable=5ms h=android.view.Choreographer$FrameHandler 
c=android.view.Choreographer$FrameDisplayEventReceiver
2020-05-20 13:19:13.543 26583-26583/com.destructo.covidtracker 

I/Choreographer: Skipped 87 frames!  The application may be doing too much 
work on its main thread.
2020-05-20 13:19:13.543 26583-26639/com.destructo.covidtracker 
I/OpenGLRenderer: Davey! duration=1498ms; Flags=0, 
IntendedVsync=645628150832172, Vsync=645628150832172, 
OldestInputEvent=9223372036854775807, NewestInputEvent=0, 
HandleInputStart=645628151894908, AnimationStart=645628152051210, 
PerformTraversalsStart=645628152055168, DrawStart=645629621464439, 
SyncQueued=645629644963710, SyncStart=645629645792251, 
IssueDrawCommandsStart=645629646424022, SwapBuffers=645629648726366, 
FrameCompleted=645629649800324, DequeueBufferDuration=188000, 
QueueBufferDuration=749000, 
2020-05-20 13:19:13.545 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: doFrame is 1465ms late because of 13 msg, msg 1 took 1495ms 
(seq=116 running=1411ms runnable=5ms late=1ms 
h=android.view.Choreographer$FrameHandler 
c=android.view.Choreographer$FrameDisplayEventReceiver)
2020-05-20 13:19:16.918 26583-26632/com.destructo.covidtracker 
I/to.covidtracke: ProcessProfilingInfo new_methods=6942 is saved 
saved_to_disk=1 resolve_classes_delay=8000
2020-05-20 13:19:40.514 26583-26583/com.destructo.covidtracker 
D/ViewRootImpl: [TouchInput][ViewRootImpl] KeyEvent { action=ACTION_DOWN, 
keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x48, repeatCount=0, 
eventTime=645656609, downTime=645656609, deviceId=-1, source=0x101, 
displayId=-1 }
2020-05-20 13:19:40.650 26583-26583/com.destructo.covidtracker 
D/ViewRootImpl: [TouchInput][ViewRootImpl] KeyEvent { action=ACTION_UP, 
keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x48, repeatCount=0, 
eventTime=645656753, downTime=645656609, deviceId=-1, source=0x101, 
displayId=-1 }
2020-05-20 13:19:41.155 26583-26601/com.destructo.covidtracker 
I/to.covidtracke: Background concurrent copying GC freed 197245(8256KB) 
AllocSpace objects, 4(72KB) LOS objects, 49% free, 22MB/45MB, paused 77us 
total 121.932ms
2020-05-20 13:20:11.708 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: Long Msg: seq=2203 plan=13:19:40.690  late=0ms wall=31018ms 
running=29292ms runnable=126ms h=android.view.Choreographer$FrameHandler 
c=android.view.Choreographer$FrameDisplayEventReceiver
2020-05-20 13:20:11.709 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: MotionEvent is 10361ms late (event_seq=4, action=ACTION_DOWN) 
because of 1 msg, msg 1 took 31018ms (seq=2203 running=29292ms runnable=126ms 
h=android.view.Choreographer$FrameHandler 
c=android.view.Choreographer$FrameDisplayEventReceiver)
2020-05-20 13:20:11.709 26583-26583/com.destructo.covidtracker 
W/InputEventReceiver: App Input: 10361ms before dispatchInputEvent 
(MotionEvent: event_seq=4, seq=1959042, action=ACTION_DOWN)
2020-05-20 13:20:11.711 26583-26639/com.destructo.covidtracker 
I/OpenGLRenderer: Davey! duration=31020ms; Flags=1, 
IntendedVsync=645656796004117, Vsync=645656796004117, 
OldestInputEvent=9223372036854775807, NewestInputEvent=0, 
HandleInputStart=645656796923387, AnimationStart=645656796961876, 
PerformTraversalsStart=645656797074585, DrawStart=645687793910146, 
SyncQueued=645687812587698, SyncStart=645687813233010, 
IssueDrawCommandsStart=645687813928896, SwapBuffers=645687815157698, 
FrameCompleted=645687817258583, DequeueBufferDuration=189000, 
QueueBufferDuration=1646000, 
2020-05-20 13:20:11.714 26583-26583/com.destructo.covidtracker 
W/InputEventReceiver: App Input: 8571ms before dispatchInputEvent 
(MotionEvent: event_seq=5, seq=1959127, action=ACTION_CANCEL)
2020-05-20 13:20:11.715 26583-26583/com.destructo.covidtracker 

I/Choreographer: Skipped 1860 frames!  The application may be doing too much 
work on its main thread.
2020-05-20 13:20:11.774 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: doFrame is 31009ms late because of 20 msg, msg 1 took 31018ms 
(seq=2203 running=29292ms runnable=126ms 
h=android.view.Choreographer$FrameHandler
c=android.view.Choreographer$FrameDisplayEventReceiver)
2020-05-20 13:20:11.776 26583-26639/com.destructo.covidtracker 
I/OpenGLRenderer: Davey! duration=31068ms; Flags=0, 
IntendedVsync=645656812653619, Vsync=645687812652379, 
OldestInputEvent=9223372036854775807, NewestInputEvent=0, 
HandleInputStart=645687822730146, AnimationStart=645687822796552, 
PerformTraversalsStart=645687865692802, DrawStart=645687866102750, 
SyncQueued=645687878454625, SyncStart=645687879030510, 
IssueDrawCommandsStart=645687880157177, SwapBuffers=645687881475146, 
FrameCompleted=645687882061812, DequeueBufferDuration=192000, 
QueueBufferDuration=255000, 

My Adapter

class GlobalCountryAdapter (private val onClickListener: GlobalClickListener):
ListAdapter<GlobalCountryStatistics, GlobalCountryAdapter.ViewHolder>(
    GlobalCountryDiffCallback()
) {


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    return ViewHolder.from(
        parent
    )
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val countryData = getItem(position)
    holder.bind(countryData)
    holder.itemView.setOnClickListener{
        onClickListener.onCLick(countryData)
    }
}


class ViewHolder private constructor(val binding: DataListItemViewBinding) : 
RecyclerView.ViewHolder(binding.root) {
    fun bind(countryData: GlobalCountryStatistics) {
        if (countryData.cases_today != null && countryData.cases_today > 0 ){
            binding.increaseIcon.visibility = View.VISIBLE
            binding.newCasesTxt.visibility = View.VISIBLE
        }else{
            binding.increaseIcon.visibility = View.GONE
            binding.newCasesTxt.visibility = View.GONE
        }
        binding.globalCountryData = countryData
        binding.executePendingBindings()
    }

    companion object {
        fun from(parent: ViewGroup): ViewHolder {
            var layoutInflater = LayoutInflater.from(parent.context)
            val binding = DataListItemViewBinding.inflate(layoutInflater, parent, false)
            return ViewHolder(
                binding
            )
        }
    }

}


class GlobalClickListener(val clickListener: (country:GlobalCountryStatistics) -> Unit){
    fun onCLick(country:GlobalCountryStatistics) = clickListener(country)
}

}

class GlobalCountryDiffCallback : DiffUtil.ItemCallback<GlobalCountryStatistics>() {
override fun areItemsTheSame(oldItem: GlobalCountryStatistics, newItem: GlobalCountryStatistics): 
Boolean {
    return oldItem.country_name == newItem.country_name
}

override fun areContentsTheSame(oldItem: GlobalCountryStatistics,
 newItem: GlobalCountryStatistics): Boolean {
    return oldItem == newItem
}

}

GlobalFragment

class GlobalFragment : Fragment() {

private val mglobalViewModel: GlobalViewModel by lazy {
    ViewModelProvider(this).get(GlobalViewModel::class.java)
}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {

    val binding = FragmentGlobalBinding.inflate(inflater)

    binding.setLifecycleOwner(this)

    binding.globalViewModel = mglobalViewModel


    mglobalViewModel.globalCountryStats.observe(viewLifecycleOwner, Observer {

        val adap = GlobalCountryAdapter(GlobalCountryAdapter.GlobalClickListener {
            mglobalViewModel.navigationToCountryDetail(it)
        })
        adap.submitList(it)
        binding.countryRecycler.adapter = adap

    })

    mglobalViewModel.navigateToCountryDetail.observe(viewLifecycleOwner, Observer {
        if (null != it){
            this.findNavController().navigate(
                GlobalFragmentDirections.actionGlobalFragmentToCountryDetailsFragment2(it))
            mglobalViewModel.doneNavigationToCountryDetail() }
    })

    mglobalViewModel.globalStats.observe(viewLifecycleOwner, Observer { globalSummary ->
        if (null != globalSummary){
            binding.include.globalMoreButton.setOnClickListener{
                this.findNavController().navigate(
                    GlobalFragmentDirections.actionGlobalFragmentToGlobalSummaryDetailsFragment(
                        globalSummary
                    )
                )
            }
        }
    })

    return binding.root
}

}

BindingAdapter for recycler view

@BindingAdapter("countryList")
fun bindCountryRecyclerView(recyclerView: RecyclerView, mdata: List<GlobalCountryStatistics>?) {

val adapter = recyclerView.adapter as FinalAdapter
mdata?.let {
    adapter.submitList(mdata)
}
}

ViewModel

class GlobalViewModel : ViewModel() {

private var _globalStats = MutableLiveData<GlobalCoronaStatistics>()
val globalStats: LiveData<GlobalCoronaStatistics>
    get() = _globalStats

private var _globalCountryStats = MutableLiveData<List<GlobalCountryStatistics>>()
val globalCountryStats:LiveData<List<GlobalCountryStatistics>>
    get() = _globalCountryStats

private val _navigateToCountryDetail = MutableLiveData<GlobalCountryStatistics>()
val navigateToCountryDetail: LiveData<GlobalCountryStatistics>
    get() = _navigateToCountryDetail

private var globalViewModelJob = Job()

private val uiScope = CoroutineScope(globalViewModelJob + Dispatchers.Main)


init {
    getGlobalStatistics()
    getCountryStatsList()
}

private fun getGlobalStatistics() {
    uiScope.launch {
        var getGlobalDataDef = GlobalApi.retrofitService.getGlobalDataAsync()
        try {

            val globalData = getGlobalDataDef.await()
            _globalStats.value = globalData
        } catch (e: Exception) {

            Log.e("GlobalViewModel", "FAILED NETWORK\n" + e.message)
        }
    }
}

private fun getCountryStatsList(){
    uiScope.launch {

        var getCountryDeferred = GlobalApi.retrofitService.getGlobalCountryDataAsync()
        try {
            val globalCountry = getCountryDeferred.await()
            _globalCountryStats.value = globalCountry
        }catch (e:Exception){
            Log.e("GlobalViewModel","FAILED NETWORK\n" + e.message)
        }
    }
}

fun navigationToCountryDetail(selectedCountry:GlobalCountryStatistics){
    _navigateToCountryDetail.value = selectedCountry
}
fun doneNavigationToCountryDetail(){
    _navigateToCountryDetail.value = null
}

override fun onCleared() {
    super.onCleared()
    globalViewModelJob.cancel()
}

}

GlobalApiService

private const val BASE_URL = "https://disease.sh/v2/"

private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()

private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.baseUrl(BASE_URL)
.build()

interface GlobalApiService {

@GET("all")
fun getGlobalDataAsync(): Deferred<GlobalCoronaStatistics>


@GET("countries?sort=cases")
fun getGlobalCountryDataAsync(): Deferred<List<GlobalCountryStatistics>>

@GET("gov/india")
fun getIndiaDataAsync(): Deferred<IndiaStatistics>
}

object GlobalApi {
val retrofitService: GlobalApiService by lazy { 
retrofit.create(GlobalApiService::class.java) }
}
Vishal Kashi
  • 103
  • 6

2 Answers2

2

New answer:

OK after pulling your repo, I see the problem. You use layout_height="wrap_content" for RecyclerViews, which leads to RecyclerView has to render all its child views at once and freeze your app. Take a look at my answer here: https://stackoverflow.com/a/57918094/10720040

Understand that you want to scroll both the information on the upper part of the screen and the RecyclerView under it, you should try to use ViewType in RecyclerView to achieve that.

Here is the layout I editted, just to let you know that solution can work:

fragment_country.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

    <variable
        name="indiaViewModel"
        type="com.destructo.corona_tracker.country.IndiaViewModel" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".global.GlobalFragment">

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="wrap_content"
        android:layout_height="275dp"
        android:src="@drawable/remote_work_man"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <include
        android:id="@+id/include"
        layout="@layout/country_stats_card"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:indiaData="@{indiaViewModel.indiaSummaryData}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView2" />

    <TextView
        android:id="@+id/textView15"
        style="@style/heading_text_medium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:alpha="0.6"
        android:text="@string/state_text"
        android:textAllCaps="true"
        app:layout_constraintBaseline_toBaselineOf="@+id/textView16"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/textView16"
        style="@style/heading_text_medium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:alpha="0.6"
        android:text="@string/infected_text"
        android:textAllCaps="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/include" />


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/stateRecycler"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="16dp"
        android:nestedScrollingEnabled="false"
        android:orientation="vertical"
        android:overScrollMode="never"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView15"
        app:stateList="@{indiaViewModel.indiaStateData}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Old answer:

Maybe GlobalApi.retrofitService methods did not run in background thread. You set Dispatchers.Main for uiScope, so there's a chance that everying inside launch {} will run on Main thread. You can notice that in the code above you still be able to call _globalStats.value = globalData inside launch, which must not be allowed if running on a background thread. Try this:

uiScope.launch {
    try {
        val globalData = withContext(Dispatchers.IO) {
             GlobalApi.retrofitService.getGlobalDataAsync() 
        }
        _globalStats.value = globalData
    } catch (e: Exception) {

        Log.e("GlobalViewModel", "FAILED NETWORK\n" + e.message)
    }
}
Miller Go Dev
  • 510
  • 4
  • 7
  • .await() does the job on a separate thread and gives the result back to us. I tried your code also but it's not working. Thanks for reply though – Vishal Kashi May 20 '20 at 16:48
  • @VishalKashi Happy to hear that! Help me to make my answer to be accepted – Miller Go Dev May 21 '20 at 04:29
  • I actually did but it says answer recorded but not shown publicly if your rep in below 15. I will up my rep may be then it will show. – Vishal Kashi May 21 '20 at 04:44
  • this answer should get hundreds of upvotes. we dont give attention such of this little cases, but this cases can make big problems in future when we work with big data. Thanks for answer! – JavadKhan May 15 '21 at 20:49
0

The same situation, I used a code structure very similar to the your code I encountered and you made. In the first, don't worry about this message the emulator is emulating being a phone so there are all sorts of messages being generated. It is just doing by your phone.