ViewPager2 and FragmentStatePagerAdapter - kotlin

I want to set the adapter of ViewPager2 to FragmentStatePagerAdapter but I get this error:
Type mismatch. Required: (RecyclerView.Adapter<RecyclerView.ViewHolder!>?..RecyclerView.Adapter<*>?) Found: ViewPager2Adapter
My ViewPagerAdapter class is
class ViewPager2Adapter(fm:FragmentManager) :FragmentStatePagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
return when(position) {
0 -> {
MyScansListFragment()
}
1 -> {
PurchasedItemsFragment()
}
else -> {
Fragment()
}
}
}
override fun getCount(): Int {
return 2
}
override fun getItemPosition(`object`: Any): Int {
return POSITION_NONE
}}
And in the oncreateView() :
val viewPager2Adapter = ViewPager2Adapter(activity?.supportFragmentManager!!)
binding!!.viewPager.adapter = viewPager2Adapter

okay, let's change the code a little bit.
First of all, FragmentStatePagerAdapter has been deprecated.
FragmentStatePagerAdapter & FragmentPagerAdapter have been recently deprecated, and your code must look something like this. FragmentStatePagerAdapter and if you get your cursor over it and see details, there will be a statement "Deprecated Switch to androidx.viewpager2.widget.ViewPager2 and use androidx.viewpager2.adapter.FragmentStateAdapter instead."
try the following code.
class ViewPager2Adapter(private val listFragment: ArrayList<Fragment>,
fm: FragmentManager,
lifecycle: Lifecycle) : FragmentStateAdapter(fm, lifecycle) {
override fun getItemCount(): Int {
return listFragment.size
}
override fun createFragment(position: Int): Fragment {
return listFragment[position]
}
}
so, this is now kind of your universal viewpager adapter.
The next thing is we require fragments to be passed in here.
//I don't think you need Fragment() but since it's there in your list.
val fragmentList = listOf(MyScansListFragment(), PurchasedItemsFragment(),Fragment())
val viewPager2Adapter = ViewPager2Adapter(fragmentList, activity?.supportFragmentManager!!, lifecycle)
binding!!.viewPager.adapter = viewPager2Adapter

Related

Room cannot verify the data integrity. Looks like you've changed schema.... What's wrong?

I am using Room in my app with two entities. The whole implementation is below.
The Problem is, the given scheme is fixed, which means I do not change anything regarding DB. When I provide a new version of my app to Users over Google Play Console, I get the following issue in Cryshlytics although I did not change anything for DB, just edited UI or another things, which definetly nothing have to do with DB:
Fatal Exception: java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
at androidx.room.RoomOpenHelper.checkIdentity(RoomOpenHelper.java:154)
at androidx.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:135)
.......
Now I am not sure if I change the version of DB, it would work. What is wrong here?
BTW the DB is called from a Fragment like this
val mainDb: MainRepository by lazy { MainRepository(requireContext()) }
val stateDb: StateRepository by lazy { StateRepository(requireContext()) }
What's wrong here?
AppDatabase:
#Database(entities = [Main::class, State::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract val mainDao: MainDao
abstract val stateDao: StateDao
companion object {
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase? =
INSTANCE ?: synchronized(AppDatabase::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
MY_DB
).allowMainThreadQueries()
.build()
return INSTANCE
}
}
}
Dao:
#Dao
interface StateDao {
#Query("SELECT * FROM $STATE")
fun getAll(): List<State>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(state: State)
#Update
fun update(state: State)
#Query("DELETE FROM $STATE")
fun drop()
}
#Dao
interface MainDao {
#Query("SELECT * FROM $MAIN")
fun getAll(): List<Main>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(main: Main)
#Update
fun update(main: Main)
#Query("DELETE FROM $MAIN")
fun drop()
}
Main:
#Entity(tableName = MAIN)
data class Main(
#PrimaryKey #ColumnInfo(name = NUMBER) val number: Int,
#ColumnInfo(name = CARD) val car: String? = EMPTY,
#ColumnInfo(name = MODEL) val model: String? = EMPTY
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString(),
parcel.readString()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(number)
parcel.writeString(car)
parcel.writeString(model)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Main> {
override fun createFromParcel(parcel: Parcel): Main {
return Main(parcel)
}
override fun newArray(size: Int): Array<Main?> {
return arrayOfNulls(size)
}
}
}
State:
#Entity(tableName = STATE)
data class State(
#PrimaryKey #ColumnInfo(name = NUMBER) val number: Int,
#ColumnInfo(name = STATE) val state: String? = EMPTY
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(number)
parcel.writeString(question)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<State> {
override fun createFromParcel(parcel: Parcel): State {
return State(parcel)
}
override fun newArray(size: Int): Array<State?> {
return arrayOfNulls(size)
}
}
}
Repository:
class MainRepository(context: Context) {
private val mainDao = AppDatabase.getInstance(context)?.mainDao
fun getAll(): List<Main>? {
return mainDao?.getAll()
}
fun insert(main: Main) {
AsyncInsert(mainDao).execute(main)
}
fun update(main: Main) {
mainDao?.update(main)
}
fun drop() {
mainDao?.drop()
}
private class AsyncInsert(private val dao: MainDao?) : AsyncTask<Main, Void, Void>() {
override fun doInBackground(vararg p0: Main?): Void? {
p0[0]?.let { dao?.insert(it) }
return null
}
}
}
class StateRepository(context: Context) {
private val stateDao = AppDatabase.getInstance(context)?.stateDao
fun drop() {
stateDao?.drop()
}
fun getAll(): List<State>? {
return stateDao?.getAll()
}
fun insert(state: State) {
AsyncInsert(stateDao).execute(state)
}
fun update(state: State) {
stateDao?.update(state)
}
private class AsyncInsert(private val dao: StateDao?) : AsyncTask<State, Void, Void>() {
override fun doInBackground(vararg p0: State?): Void? {
p0[0]?.let { dao?.insert(it) }
return null
}
}
}
Now I am not sure if I change the version of DB, it would work. What is wrong here?
Changing the version would probably not work as the schema, as far as Room is concerned, has changed.
There is either a bug or the schema has been changed.
However, changing the version, would, with a Migration that does nothing (so as to not get a "no migration specified" error), then fail but importantly with an expected (what Room expects the schema to be according to the Entities) found (the schema that exists) discrepancy. This, if there is no bug, could then be used to ascertain what has been changed.

How to use LifecycleScope to execute coroutine

I am discovering Kotlin and android app dev. I fail to get data from my room database (because of Cannot access database on the main thread). So I try with lifecyclescope.
The concerned code, in Fragment onViewCreated function, is :
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
The called function (in viewModel) is :
fun get() = viewModelScope.launch {
repository.get()
}
There is the "full" code (simplified), Entity & DAO :
#Entity
data class AccountConfiguration(
#PrimaryKey val server_address: String,
#ColumnInfo(name = "user_name") val user_name: String,
// [...]
)
#Dao
interface AccountConfigurationDao {
#Query("SELECT * FROM accountconfiguration LIMIT 1")
fun flow(): Flow<AccountConfiguration?>
#Query("SELECT * FROM accountconfiguration LIMIT 1")
suspend fun get(): AccountConfiguration?
// [...]
}
Repository :
package fr.bux.rollingdashboard
import androidx.annotation.WorkerThread
import kotlinx.coroutines.flow.Flow
class AccountConfigurationRepository(private val accountConfigurationDao: AccountConfigurationDao) {
val accountConfiguration: Flow<AccountConfiguration?> = accountConfigurationDao.flow()
// [...]
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun get() : AccountConfiguration? {
return accountConfigurationDao.get()
}
}
ViewModel & Factory :
class AccountConfigurationViewModel(private val repository: AccountConfigurationRepository) : ViewModel() {
val accountConfiguration: LiveData<AccountConfiguration?> = repository.accountConfiguration.asLiveData()
// [...]
fun get() = viewModelScope.launch {
repository.get()
}
// [...]
}
class AccountConfigurationViewModelFactory(private val repository: AccountConfigurationRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AccountConfigurationViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return AccountConfigurationViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Fragment :
class AccountConfigurationFragment : Fragment() {
private var _binding: AccountConfigurationFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private val viewModel: AccountConfigurationViewModel by activityViewModels {
AccountConfigurationViewModelFactory(
(activity?.application as RollingDashboardApplication).account_configuration_repository
)
}
lateinit var accountConfiguration: AccountConfiguration
// [...]
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonGoBackMain.setOnClickListener {
findNavController().navigate(R.id.action_AccountConfigurationFragment_to_DashboardFragment)
}
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
binding.buttonSave.setOnClickListener {
save()
}
}
// [...]
}
In your current code,
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
viewModel.get() is not a suspend function, so it returns immediately and proceeds to the next line. It actually returns the Job created by viewModelScope.launch().
If you want your coroutine to wait for the result before continuing you should make the get() function suspend and return the AccountConfiguration?
suspend fun get(): AccountConfiguration? {
return repository.get()
}
You need not change dispatchers to Dispatchers.Default because Room itself will switch to a background thread before executing any database operation.
Right now if there is a configuration change while coroutines inside lifecyclerScope are running, everything will get cancelled and restarted.
A better way would have been to put the suspending calls inside the ViewModel and expose a LiveData/Flow to the UI.
The problem is the viewModel function :
fun get() = viewModelScope.launch {
repository.get()
}
This function must be the coroutine instead launch the coroutine itself. Correct code is :
suspend fun get(): AccountConfiguration? {
return repository.get()
}

Is it possible to verify at compile time whether the required function is called for the Factory Class in Kotlin?

class ModelFactory {
fun setA() : ModelFactory {
// blabla...
}
fun setB() : ModelFactory {
// blabla...
}
fun setC() : ModelFactory {
// blabla...
}
fun build() : Model {
// An error occurs if any of setA, setB, and setC is not called.
}
}
//example
fun successTest() {
ModelFactory().setA().setB().setC().build() // No error occurs at compile time
}
fun failTest() {
ModelFactory().setA().build() // An error occurs at compile time because setB and setC are not called.
}
It's awkward grammatically, but I think it's been expressed what I want.
I have already implemented an error-raising runtime for this requirement, but I want to check this at compile time.
If possible, I think I should use annotations. But is this really possible at compile time?
With Kotlin, I have been avoiding builder pattern, as we can always specify default values for non-mandatory fields.
If you still want to use a builder pattern, you can use Step builder pattern that expects all mandatory fields to be set before creating the object. Note that each setter method returns the reference of next setter interface. You can have multiple Step builders based on the combination of mandatory fields.
class Model(val a: String = "", val b: String = "", val c: String = "")
class StepBuilder {
companion object {
fun builder(): AStep = Steps()
}
interface AStep {
fun setA(a: String): BStep
}
interface BStep {
fun setB(b: String): CStep
}
interface CStep {
fun setC(c: String): BuildStep
}
interface BuildStep {
//fun setOptionalField(x: String): BuildStep
fun build(): Model
}
class Steps : AStep, BStep, CStep, BuildStep {
private lateinit var a: String
private lateinit var b: String
private lateinit var c: String
override fun setA(a: String): BStep {
this.a = a
return this
}
override fun setB(b: String): CStep {
this.b = b
return this
}
override fun setC(c: String): BuildStep {
this.c = c
return this
}
override fun build() = Model(a, b , c)
}
}
fun main() {
// cannot build until you call all three setters
val model = StepBuilder.builder().setA("A").setB("B").setC("C").build()
}

Scope Resolution Operator in Kotlin

I have read the following syntax. I have no idea why scope resolution operator is used in it.
class XyzFragment : Fragment() {
lateinit var adapter: ChatAdapter
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
if (!::adapter.isInitialized) { <-- This one
adapter = ChatAdapter(this, arrayListOf())
}
}
}
I want to know what is :: in if (!::adapter.isInitialized) { statement.
:: is a short form for this:: in Kotlin.
:: is a operator to creates a member reference or a class reference. For example,
class Test {
fun foo() {
}
fun foo2(value: Int) {
}
fun bar() {
val fooFunction = ::foo
fooFunction.invoke() // equals to this.foo()
val foo2Function = ::foo2
foo2Function.invoke(1) // equals to this.foo2(1)
val fooFunction2 = Test::foo
val testObject = Test()
fooFunction2.invoke(this) // equals to this.foo()
fooFunction2.invoke(testObject) // equals to testObject.foo()
}
}
This is mainly used in reflection and passing function.

Kotlin - RecyclerView.ViewHolder subclass - unable to access an extra property

I have implemented RecyclerView.ViewHolder sub-class as below:
class PersonViewHolder(itemView: View, binding: ViewDataBinding) : RecyclerView.ViewHolder(itemView) { }
Now I am trying to access binding property declared in it like this within subclass of RecyclerView.Adapter:
override fun onBindViewHolder(holder: PersonViewHolder?, position: Int) {
val person = persons[position]
if (holder != null) {
holder.binding.setVariable(BR.person, person) // line with error
holder.binding.executePendingBindings() // line with error
}
}
But compiler is complaining - Unresolved reference: binding
Here is the complete implementation:
class PersonsAdapter(private var persons: Array<Person>) : RecyclerView.Adapter<PersonsAdapter.PersonViewHolder>() {
override fun onBindViewHolder(holder: PersonViewHolder?, position: Int) {
val person = persons[position]
if (holder != null) {
holder.binding.setVariable(BR.person, person)
holder.binding.executePendingBindings()
}
}
override fun getItemCount(): Int {
return persons.size
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): PersonViewHolder {
val itemView = LayoutInflater.from(parent!!.context).inflate(R.layout.list_item_person, parent, false)
return PersonViewHolder(itemView, DataBindingUtil.bind(itemView))
}
class PersonViewHolder(itemView: View, binding: ViewDataBinding) : RecyclerView.ViewHolder(itemView) { }
}
Any ideas if I am missing anything over here? Please suggest.
binding: ViewDataBinding - you're only defining a constructor parameter, it is never saved as a member of the class. Mark it using var or val to have it store the parameter and have it be accessible later on.
check build.gradle file (module level) in your project
in the top android extesion plugin is included or not?
plugin {
id 'kotlin-android-extensions'
}