Jouan H. Sulaiman
Jouan H. Sulaiman

Reputation: 117

RecyclerView skipping layout and lagging

I'm sorry to ask the repeatedly answered question but I just couldn't solve this relating to my specific case, maybe I'm missing something. The error is E/RecyclerView: No adapter attached; skipping layout and I'm not sure, is the problem with an adapter I set or the RecyclerView per se? Also, I was following a tutorial and this was the code that was presented.

(I tried brining the initRecyclerView() into the main onCreateView but no luck. Some answers say to set an empty adapter first and notify it with the changes later but I don't know how to do that.) This is my HomeFragment:

open class HomeFragment() : Fragment() {
    private lateinit var homeViewModel: HomeViewModel
    private lateinit var binding: FragmentHomeBinding

    private val language = arrayOf("English", "German", "Arabic", "Spanish", "Chinese", "French")


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

        val dao = VocabData.getInstance(this).VocabDao
        val repository = VocabRepository(dao)
        val factory = HomeViewModelFactory(repository, application = activity?.applicationContext as Application)
        homeViewModel = ViewModelProvider(this, factory).get(HomeViewModel::class.java)
        binding.myViewModel = homeViewModel
        binding.lifecycleOwner = this
        initRecyclerView()


        homeViewModel.message.observe(viewLifecycleOwner, Observer {
            it.getContentIfNotHandled()?.let {
                Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show()
            }
        })

        // create an adapter
        val arrayAdapter =
            ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, language)
        binding.spinner.adapter = arrayAdapter
        // Set layout to use when the list of choices appear
        arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        // Set Adapter to Spinner
        binding.spinner.setAdapter(arrayAdapter)

        val button = binding.tagButton

        button.setOnClickListener{
        GlobalScope.launch(Dispatchers.IO) {
            homeViewModel.tagger(binding.spinner)
              }
        }

        return binding.root
    }

    private fun initRecyclerView(){
        binding.vocabRecyclerView.layoutManager = LinearLayoutManager(requireContext())
        displayVocabsList()
    }

    private fun displayVocabsList() {
    homeViewModel.vocabs.observe(viewLifecycleOwner, Observer {
        Log.i("MYTAG", it.toString())
        binding.vocabRecyclerView.adapter = MyRecyclerViewAdapter(it, { selectedItem: Vocab -> listItemClicked(selectedItem) })
    })

    }
    
    private fun listItemClicked(vocab: Vocab){
        Toast.makeText(requireContext(), "selected sentence is ${vocab.sentString}", Toast.LENGTH_LONG).show()
    }
}

And this is my RecyclerViewAdapter, which is really just boilerplate code:

class MyRecyclerViewAdapter(private val vocabsList: List<Vocab>, private val clickListener:(Vocab)->Unit) :
    RecyclerView.Adapter<MyViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding : ListItemBinding =
            DataBindingUtil.inflate(layoutInflater, R.layout.list_item, parent, false)
        return MyViewHolder(binding)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(vocabsList[position], clickListener)
    }

    override fun getItemCount(): Int {
        return vocabsList.size
    }
}

class MyViewHolder(private val binding: ListItemBinding):RecyclerView.ViewHolder(binding.root){
    fun bind(vocab: Vocab, clickListener:(Vocab)->Unit){
        binding.sentenceTextView.text = vocab.sentString
        binding.listItemLayout.setOnClickListener{
            clickListener(vocab)
        }
    }
}

I think that's all the related code. If you have an idea please let me know how to avoid this error/lag, thank you!

UPDATE

activity_main.xml

<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" >

        <androidx.viewpager2.widget.ViewPager2
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </com.google.android.material.bottomnavigation.BottomNavigationView>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        class = "androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

fragment_home.xml

<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="myViewModel"
            type="com.jwanhsulaiman.talktag.ui.home.HomeViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="8dp"
        tools:context=".ui.home.HomeFragment">

        <EditText
            android:id="@+id/editTextTextMultiLine"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:ems="10"
            android:gravity="start|top"
            android:hint="Input Sentence(s)\n"
            android:inputType="textMultiLine"
            android:text="@={myViewModel.inputVocab}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/spinner" />

        <Button
            android:id="@+id/tag_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:enabled="@{myViewModel.enabled}"
            android:text="@={myViewModel.tagAll}"
            android:textSize="14sp"
            android:textStyle="bold"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/editTextTextMultiLine" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/vocab_recycler_view"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/progressBar" />

        <Button
            android:id="@+id/clear"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginLeft="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginRight="8dp"
            android:onClick="@{()->myViewModel.deleteAll()}"
            android:text="CLEAR"
            app:layout_constraintEnd_toStartOf="@+id/progressBar"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/editTextTextMultiLine" />

        <Spinner
            android:id="@+id/spinner"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginRight="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginRight="8dp"
            android:visibility="@{!myViewModel.barProgress}"
            app:layout_constraintEnd_toStartOf="@+id/tag_button"
            app:layout_constraintTop_toBottomOf="@+id/editTextTextMultiLine" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

HomeViewModel.kt

import android.annotation.SuppressLint
import android.app.Application
import android.view.View
import android.widget.Spinner
import androidx.databinding.Bindable
import androidx.databinding.BindingAdapter
import androidx.databinding.Observable
import androidx.databinding.ObservableField
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.jwanhsulaiman.talktag.Event
import com.jwanhsulaiman.talktag.R
import com.jwanhsulaiman.talktag.database.Vocab
import com.jwanhsulaiman.talktag.database.VocabRepository
import edu.stanford.nlp.tagger.maxent.MaxentTagger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import opennlp.tools.sentdetect.SentenceDetectorME
import opennlp.tools.sentdetect.SentenceModel


@BindingAdapter("android:visibility")
fun setVisibility(view: View, visible: Boolean) {
    view.visibility = if (visible) View.INVISIBLE else View.VISIBLE
}

@BindingAdapter("android:enabled")
fun setEnabled(view: View, enabled: Boolean) {
    view.isEnabled = !enabled
}

@SuppressLint("StaticFieldLeak")
class HomeViewModel(private val repository: VocabRepository, application: Application) : AndroidViewModel(
    application
), Observable {

    private val context = getApplication<Application>().applicationContext
    private val model: SentenceModel = SentenceModel(context.resources.openRawResource(R.raw.en))
    private val sDetector = SentenceDetectorME(model)

    private var senlist = mutableListOf<String?>()

    val vocabs = repository.vocabs


    @Bindable
    var barProgress = ObservableField<Boolean>()


    @Bindable
    var enabled = ObservableField<Boolean>()

    private fun makeVisible(){
        this.barProgress.set(true) }
    private fun makeInvisible(){
        this.barProgress.set(false) }

    private fun makeEnabled(){
        this.enabled.set(true) }
    private fun makeDisabled(){
        this.enabled.set(false) }


    @Bindable
    val inputVocab = MutableLiveData<String>()

    @Bindable
    val tagAll = MutableLiveData<String>()


    private val statusMessage = MutableLiveData<Event<String>>()

    val message : LiveData<Event<String>>
        get() = statusMessage

    init {
        tagAll.postValue("Tag!")
    }



    suspend fun tagger(spinner: Spinner){
        if (inputVocab.value.isNullOrBlank()) {
            statusMessage.postValue(Event("Please enter sentence"))
        } else {
       withContext(Dispatchers.IO) {
           //tag words
           makeEnabled()
           makeVisible()
           }
       }
    }

    private suspend fun splitSens(vocab: String): MutableList<String?> {
        withContext(Dispatchers.IO) {

            //split sentences
        }
        return senlist
    }


    private suspend fun tagAll(vocab: String){
       withContext(Dispatchers.IO){
           insert(Vocab(0, vocab))
           inputVocab.postValue(null)
       }
        makeInvisible()
        makeDisabled()
    }

    fun insert(vocab: Vocab) : Job = viewModelScope.launch {
        repository.insert(vocab)
        statusMessage.value = Event("Vocab inserted successfuly")
    }

    fun update(vocab: Vocab) : Job = viewModelScope.launch {
        repository.update(vocab)
    }

    fun delete(vocab: Vocab) : Job = viewModelScope.launch {
        repository.delete(vocab)
    }

    fun deleteAll() = viewModelScope.launch {
        repository.deleteAll()
    }

    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
        //TODO("Not yet implemented")
    }

    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
        //TODO("Not yet implemented")
    }
}

build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'

/**
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
}
**/

android {
    compileSdkVersion 29
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.jwanhsulaiman.talktag"
        minSdkVersion 16
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        multiDexEnabled true

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        dataBinding true
    }
}

dependencies {
    implementation files('libs/postagger.jar')
    implementation files('libs/nlp/opennlp-tools-1.9.3.jar')
    def lifecycle_version = "2.2.0"
    def room_version = "2.2.3"
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
    implementation 'androidx.navigation:navigation-fragment:2.3.3'
    implementation 'androidx.navigation:navigation-ui:2.3.3'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.3'
    implementation 'androidx.room:room-runtime:2.2.6'
    testImplementation 'junit:junit:4.13.2'

    kapt "com.android.databinding:compiler:3.5.0"


    implementation 'androidx.fragment:fragment-ktx:1.3.0'

    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    // Annotation processor
    //noinspection LifecycleAnnotationProcessorWithJava8
    kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"

    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

    // optional - Kotlin Extensions and Coroutines support for Room
    implementation "androidx.room:room-ktx:$room_version"
    //coroutines
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'


    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    kapt 'androidx.room:room-compiler:2.2.6'
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0"

// Kotlin Extensions and Coroutines support for Room
    implementation "androidx.room:room-ktx:2.2.6"

    implementation 'com.android.support:multidex:1.0.3'
}

Upvotes: 0

Views: 425

Answers (1)

ande
ande

Reputation: 319

Ok, it's normal you have this message because in your code, you' ll do this :

homeViewModel.vocabs.observe(viewLifecycleOwner, Observer {
        Log.i("MYTAG", it.toString())
        binding.vocabRecyclerView.adapter = MyRecyclerViewAdapter(it, { selectedItem: Vocab -> listItemClicked(selectedItem) })
    })

The best solution to not have this message is to set an adapter in createView :

myRecyclerViewAdapter = MyRecyclerViewAdapter({ selectedItem: Vocab -> listItemClicked(selectedItem) })
binding.vocabRecyclerView.adapter = myRecyclerViewAdapter

and :

homeViewModel.vocabs.observe(viewLifecycleOwner, Observer {
   myRecyclerViewAdapter.addData(it)
}

and for recyclerView :

class MyRecyclerViewAdapter : .... {

private var values = arrayListOf<....>()

fun addData(values : List<>){
   values.addAll(values)
}
}

NB: I put addAll but I don't know what you do, may be you have to do something else. You have this message because there's no adapter. You have an adpater only when there're data with your observe. It's better if you initialize your adapter and after listening for new datas.

}

Upvotes: 1

Related Questions