Wrong UninitializedPropertyAccessException in Kotlin after property initialization - android

I have Dagger2 injected property in my presenter.
#Inject lateinit var dataInteractor: DataInteractor
It is accessed in couple of methods. In one of them loadAppointments() everything works fine but in another refund() UninitializedPropertyAccessException is thrown. The code has been working well for a while and this issue has raised the only couple of days ago.
No Kotlin updates were installed before.
import android.util.Log
import co.example.Application
import co.example.domain.model.entity.AppointmentsEntity
import co.example.domain.model.entity.ProvidersEntity
import co.example.interactor.data.DataInteractor
import co.example.domain.di.base.RxDisposablePresenter
import co.example.view.operation.OperationView
import io.reactivex.android.schedulers.AndroidSchedulers
import java.util.*
import javax.inject.Inject
class AppointmentsPresenter : RxDisposablePresenter<AppointmentsPresenter.View>() {
#Inject
lateinit var dataInteractor: DataInteractor
private lateinit var operationView: OperationView<*>
private val results: ArrayList<AppointmentsEntity.AppointmentEntity> = ArrayList()
private val resultsHistory: ArrayList<AppointmentsEntity.AppointmentEntity> = ArrayList()
override fun onTakeView(view: View?) {
super.onTakeView(view)
operationView = view?.operationView()!!
}
fun loadAppointments() {
val userID = Application.appComponent.userInternalStorage().userID()
if (userID != null) {
add(dataInteractor
.getAppointments(userID, LIMIT, OFFSET)
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { operationView.showProgress() }
.doFinally {
operationView.hideProgress()
view?.onAppointmentsDataFilled(results, resultsHistory)
}
.subscribe(
{
divideUpcomingAndCompleteAppointments(it)
view?.unblockAppointments()
},
{
val message = it.message
if (message!!.contains("blocked")) {
view?.blockAppointments()
operationView.showError("Error")
return#subscribe
}
operationView.showError(message)
}
))
}
}
fun refund(position: Int?) {
add(dataInteractor
.refund(results[position!!].id!!)
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { operationView.showProgress() }
.doFinally {
operationView.hideProgress()
}
.subscribe(
{
view?.onRefundCompleted()
},
{
val message = it.message
operationView.showError(message!!)
}
))
}
private fun divideUpcomingAndCompleteAppointments(appointmentsEntity: AppointmentsEntity) {
results.clear()
resultsHistory.clear()
for (appointment in appointmentsEntity.results!!) {
if (appointment.status.equals(STATUS_COMPLETE, true)) {
resultsHistory.add(appointment)
} else {
results.add(appointment)
}
}
}
fun loadProviderById(doctorId: Long?, onDoctorRetrivedListener: (ProvidersEntity.ProviderEntity) -> Unit) {
add(
dataInteractor
.getProviders(0, 100, 0, 0)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ providersEntity ->
val doctor = providersEntity
.results
?.filter {
Log.d("ATAT", "loadProviderById: ${it.user_id}")
it.user_id == doctorId
}
doctor?.let {
if (it.isNotEmpty()) {
onDoctorRetrivedListener.invoke(doctor[0])
}
}
},
{
val message = it.message
operationView.showError(message!!)
}))
}
interface View {
fun operationView(): OperationView<*>
fun onAppointmentsDataFilled(appointments: ArrayList<AppointmentsEntity.AppointmentEntity>?,
appointmentsHistory: ArrayList<AppointmentsEntity.AppointmentEntity>?)
fun onRefundCompleted()
fun blockAppointments()
fun unblockAppointments()
}
companion object {
const val STATUS_COMPLETE = "Complete"
const val LIMIT: Long = 1000
const val OFFSET: Long = 0
}
}
Here is the stack trace:
2018-12-13 11:59:29.359 4808-4808/co.example E/AndroidRuntime: FATAL EXCEPTION: main
Process: co.example, PID: 4808
kotlin.UninitializedPropertyAccessException: lateinit property dataInteractor has not been initialized
at co.example.presenter.appointements.AppointmentsPresenter.refund(AppointmentsPresenter.kt:59)
at co.example.view.appointements.AppointmentsFragment.refundRequest(AppointmentsFragment.kt:123)
at co.example.activity.MainActivity.onRefundRequested(MainActivity.kt:295)
at co.example.view.dialog.RefundDialogFragment$onCreateDialog$1.onClick(RefundDialogFragment.kt:16)
at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:172)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
UPD:
Doesn't reproduce if fragment that calls these methods is created as singleton.

Related

How to test ViewModel if flow from RemoteMediator (paging 3) library has been collected or implemented in this?

I am getting this exception while unit testing the viewModel.
Exception in thread "UI thread #coroutine#1" java.lang.NullPointerException: Parameter specified as non-null is null: method androidx.paging.CachedPagingDataKt.cachedIn, parameter <this>
at androidx.paging.CachedPagingDataKt.cachedIn(CachedPagingData.kt)
at com.sarmad.newsprism.news.ui.NewsViewModel$getNewsStream$1.invokeSuspend(NewsViewModel.kt:46)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [CoroutineId(1), "coroutine#1":StandaloneCoroutine{Cancelling}#67526363, Dispatchers.Main]
expected:<false> but was:<true>
Expected :false
Actual :true
I want to test if newsViewModel.getNewsStream() is called, it should start loading, stop loading and expose updated UiState to NewsFragment so fragment can call adapter.submitData(data). But, there is an exception indication an error (I am a beginner and I can't understand that even after research for a good amount of time) in cachedIn(viewModelScope) operator while I am collecting flow in viewModel.
NewsViewModel
package com.sarmad.newsprism.news.ui
#HiltViewModel
class NewsViewModel #Inject constructor(
private val newsRepository: NewsRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
companion object {
const val KEY_SUBREDDIT = "us"
const val DEFAULT_SUBREDDIT = "androiddev"
}
init {
if (!savedStateHandle.contains(KEY_SUBREDDIT)) {
savedStateHandle[KEY_SUBREDDIT] = DEFAULT_SUBREDDIT
}
}
#OptIn(ExperimentalCoroutinesApi::class)
val articles = savedStateHandle.getLiveData<String>(KEY_SUBREDDIT)
.asFlow()
.flatMapLatest {
newsRepository.getBreakingNewsStream(it)
}.cachedIn(viewModelScope)
private val _userMessage = MutableStateFlow<String?>(null)
private val _isLoading = MutableStateFlow(false)
private val _newsArticles = articles
val uiState: StateFlow<NewsItemListUiState> = combine(
_isLoading, _userMessage, _newsArticles
) { isLoading, userMessage, newsArticles ->
NewsItemListUiState(
news = newsArticles,
isLoading = isLoading,
userMessage = userMessage
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000L),
initialValue = NewsItemListUiState(isLoading = true)
)
fun refresh() {
_isLoading.value = true
viewModelScope.launch {
newsRepository.refreshTasks()
_isLoading.value = false
}
}
}
NewsRepository
package com.sarmad.newsprism.data.repository
import androidx.paging.PagingData
import com.sarmad.newsprism.data.entities.NewsResponse
import kotlinx.coroutines.flow.Flow
import com.sarmad.newsprism.data.Result
import com.sarmad.newsprism.data.entities.Article
interface NewsRepository {
suspend fun getSearchedNewsStream(searchQuery: String, pageNumber: Int):
Flow<NewsResponse>
suspend fun getBreakingNewsStream(countryCode: String): Flow<PagingData<Article>>
}
NewsRepositoryImpl
package com.sarmad.newsprism.data.repository
import android.util.Log
import androidx.paging.*
import com.sarmad.newsprism.data.Result
import com.sarmad.newsprism.data.entities.Article
import com.sarmad.newsprism.data.entities.NewsResponse
import com.sarmad.newsprism.data.localdatasource.ArticleDao
import com.sarmad.newsprism.data.localdatasource.ArticleDatabase
import com.sarmad.newsprism.data.localdatasource.RemoteKeysDao
import com.sarmad.newsprism.data.paging.mediaters.NewsRemoteMediator
import com.sarmad.newsprism.data.remotedatasource.api.NewsApi
import com.sarmad.newsprism.utils.Constants.Companion.PAGING_CONFIG_PAGE_SIZE
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.mapLatest
import javax.inject.Inject
private const val TAG = "NewsRepositoryImpl"
class NewsRepositoryImpl #Inject constructor(
private val api: NewsApi,
private val articleDao: ArticleDao,
private val articleDatabase: ArticleDatabase,
private val remoteKeysDao: RemoteKeysDao,
) : NewsRepository {
override suspend fun getSearchedNewsStream(
searchQuery: String,
pageNumber: Int
): Flow<NewsResponse> = flow {
val searchedNewsResponse = api.searchNews(searchQuery, pageNumber)
if (searchedNewsResponse.isSuccessful) searchedNewsResponse.body()
?.let { newsList -> emit(newsList) }
else emptyFlow<NewsResponse>()
}
#OptIn(ExperimentalPagingApi::class)
override suspend fun getBreakingNewsStream(
countryCode: String
): Flow<PagingData<Article>> {
return Pager(
config = PagingConfig(
pageSize = PAGING_CONFIG_PAGE_SIZE
),
remoteMediator = NewsRemoteMediator(articleDatabase, articleDao, remoteKeysDao, api),
pagingSourceFactory = { articleDao.getNewsStream() }
).flow
}
}
NewsRemoteMadiator
package com.sarmad.newsprism.data.paging.mediaters
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.sarmad.newsprism.data.remotedatasource.api.NewsApi
import com.sarmad.newsprism.data.entities.Article
import com.sarmad.newsprism.data.entities.ArticleRemoteKey
import com.sarmad.newsprism.data.localdatasource.ArticleDao
import com.sarmad.newsprism.data.localdatasource.ArticleDatabase
import com.sarmad.newsprism.data.localdatasource.RemoteKeysDao
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.ceil
#OptIn(ExperimentalPagingApi::class)
class NewsRemoteMediator #Inject constructor(
private val articleDatabase: ArticleDatabase,
private val articleDao: ArticleDao,
private val remoteKeysDao: RemoteKeysDao,
private val api: NewsApi
) : RemoteMediator<Int, Article>() {
override suspend fun initialize(): InitializeAction {
val newsCacheTimeout = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)
val isSkipRefresh = remoteKeysDao.getLastUpdateTime()?.let {
System.currentTimeMillis() - it >= newsCacheTimeout
}
return if (isSkipRefresh == true) {
InitializeAction.SKIP_INITIAL_REFRESH
} else {
InitializeAction.LAUNCH_INITIAL_REFRESH
}
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Article>
): MediatorResult {
return try {
val currentPage = when (loadType) {
LoadType.REFRESH -> {
val remoteKey = getRemoteKeyClosestToCurrentPosition(state)
remoteKey?.nextPage?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKey = getRemoteKeyForFirstItem(state)
val prevPage = remoteKey?.prevPage ?: return MediatorResult.Success(
remoteKey != null
)
prevPage
}
LoadType.APPEND -> {
val remoteKey = getRemoteKeyForLastItem(state)
val nextPage =
remoteKey?.nextPage
?: return MediatorResult.Success(remoteKey != null)
nextPage
}
}
val response = api.getBreakingNews("us", currentPage)
val totalPages = response.body()?.totalResults?.toDouble()?.div(20)?.let { pages ->
ceil(pages)
}?.toInt()
val endOfPaginationReached = totalPages == currentPage
val nextPage = if (endOfPaginationReached) null else currentPage.plus(1)
val prevPage = if (currentPage == 1) null else currentPage.minus(1)
articleDatabase.withTransaction {
if (loadType == LoadType.REFRESH) {
articleDao.deleteAllArticles()
remoteKeysDao.deleteAllArticleRemoteKeys()
}
response.body()?.let { response ->
val keys = articleDao.insertAll(response.articles)
val mappedKeysToArticles = keys.map { key ->
ArticleRemoteKey(
id = key.toInt(),
nextPage = nextPage,
prevPage = prevPage,
modifiedAt = System.currentTimeMillis()
)
}
remoteKeysDao.insertArticleRemoteKeys(mappedKeysToArticles)
}
}
MediatorResult.Success(endOfPaginationReached)
} catch (ex: java.lang.Exception) {
return MediatorResult.Error(ex)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Article>
): ArticleRemoteKey? {
return state.anchorPosition?.let { pos ->
state.closestItemToPosition(pos)?.id?.let { id ->
remoteKeysDao.getArticleRemoteKey(id)
}
}
}
private suspend fun getRemoteKeyForFirstItem(
state: PagingState<Int, Article>
): ArticleRemoteKey? {
return state.pages.firstOrNull {
it.data.isNotEmpty()
}?.data?.firstOrNull().let {
it?.let { it1 -> remoteKeysDao.getArticleRemoteKey(it1.id) }
}
}
private suspend fun getRemoteKeyForLastItem(
state: PagingState<Int, Article>
): ArticleRemoteKey? {
return state.pages.lastOrNull {
it.data.isNotEmpty()
}?.data?.lastOrNull().let {
it?.let { it1 -> remoteKeysDao.getArticleRemoteKey(it1.id) }
}
}
}
NewsViewModelTest
package com.sarmad.newsprism.news.ui
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.sarmad.newsprism.data.remotedatasource.api.NewsApi
import com.sarmad.newsprism.data.repository.NewsRepository
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
internal class NewsViewModelTest {
#Mock
lateinit var newsRepository: NewsRepository
#Mock
private lateinit var newsApi: NewsApi
private lateinit var newsViewModel: NewsViewModel
#OptIn(DelicateCoroutinesApi::class)
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
#get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
#OptIn(ExperimentalCoroutinesApi::class)
#Before
fun setUp() {
MockitoAnnotations.openMocks(this)
Dispatchers.setMain(mainThreadSurrogate)
newsViewModel = NewsViewModel(newsRepository)
}
#OptIn(ExperimentalCoroutinesApi::class)
#After
fun tearDown() {
Dispatchers.resetMain() // reset the main dispatcher to the original Main dispatcher
mainThreadSurrogate.close()
}
#OptIn(ExperimentalCoroutinesApi::class)
#Test
fun test_getNewsFlow() = runTest {
newsViewModel.getNewsStream("us")
assertEquals(true, newsViewModel.newsFlow.value.isLoading)
advanceUntilIdle()
assertEquals(
false,
newsViewModel.newsFlow.value.isLoading
)
assertNotNull(
newsViewModel.newsFlow.value.news
)
}
}
The best way to check a call is using a Mock.
Create an interface for your NewsViewModel like INewsViewModel and inject it by using the constructor or the Setup. Depending on your Mock package it can be created like:
//Implementation using Moq
Mock<INewsViewModel> mock = new Mock<INewsViewModel>();
mock.Setup(m => m.getNewsStream());
// Your test
mock.VerifyAll();
Moq also allows to create a Mock when the mocked class has an empty constructor.

android doesn't go into the coroutine

Good afternoon, I ran into the following problem: I'm trying to get a list of stores, but I get null in response.
judging by the debag, it does not even go into the coroutine in DataAccessStrategy.
A similar piece of code elsewhere in the app works, I can't figure out what the problem is.
LoginViewModel
class LoginViewModel #ViewModelInject constructor(private val remoteDataSource: OrderRemoteDataSource,
private val ordersRepository: OrdersRepository) :
ViewModel() {
var shops = ordersRepository.getShops()
fun showDialog(){
var shopList: List<Shop>? = shops.value?.data
var names:List<String?> = shopList!!.map{it.customName}
MaterialAlertDialogBuilder(LOGIN_ACTIVITY)
.setTitle("Выбрать магазин")
.setItems(names.toTypedArray()) { dialog, which ->
Log.d("LOGIN VIEW MODEL", "selected")
}
.show()
}
DataAccessStrategy
fun <T, A> performGetOperation(
databaseQuery: () -> LiveData<T>,
networkCall: suspend () -> Resource<A>,
saveCallResult: suspend (A) -> Unit
): LiveData<Resource<T>> =
liveData(Dispatchers.IO) {
emit(Resource.loading())
val source = databaseQuery.invoke().map { Resource.success(it) }
emitSource(source)
val responseStatus = networkCall.invoke()
if (responseStatus.status == SUCCESS) {
saveCallResult(responseStatus.data!!)
} else if (responseStatus.status == ERROR) {
emit(Resource.error(responseStatus.message!!))
emitSource(source)
}
}
OdredsRepository
class OrdersRepository #Inject constructor(
private val remoteDataSource: OrderRemoteDataSource,
private val localDataSource: OrdersDao
) {
fun getShops() = performGetOperation(
databaseQuery = { localDataSource.getAllShops() },
networkCall = { remoteDataSource.getShops() },
saveCallResult = { localDataSource.insertShop(it.results) }
)
}
OrderRemoteDataSource
class OrderRemoteDataSource #Inject constructor(
private val orderService: OrderService
) : BaseDataSource() {
suspend fun getShops() = getResult { orderService.getAllShops() }
OrderService
interface OrderService {
#GET("shops/all")
suspend fun getAllShops(): Response<ResultShops>
companion object {
const val MAUMarket_API_URL = "MYURL.com"
}
}
E/AndroidRuntime: FATAL EXCEPTION: main
Process: ru.gkomega.navigation, PID: 4349
java.lang.NullPointerException
at ru.gkomega.maumarket.ui.login.LoginViewModel.showDialog(LoginViewModel.kt:36)
at ru.gkomega.maumarket.ui.login.LoginFragment$onViewCreated$1.onClick(LoginFragment.kt:50)
at android.view.View.performClick(View.java:7259)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
at android.view.View.performClickInternal(View.java:7236)
at android.view.View.access$3600(View.java:801)
at android.view.View$PerformClick.run(View.java:27892)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
I will be glad of any help, thanks
private fun obser() {
mViewModel.shopsLiveData.observe(viewLifecycleOwner, Observer {
when (it.status) {
Resource.Status.SUCCESS -> {
//binding.progressBar.visibility = View.GONE
main.isRefreshing = false
if (!it.data.isNullOrEmpty()) Log.i("LEL",it.data.toString())
}
Resource.Status.ERROR ->
Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
Resource.Status.LOADING -> {
main.isRefreshing = true
//binding.progressBar.visibility = View.VISIBLE
}
}
})
}
Try it sample code. May it not better way, but its may be working

How to inject viewModel into Fragment?

I am trying to inject the below ViewModel into my fragment using koin.
package com.example.koinapplication.ui.main
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.koinapplication.models.Dog
import com.example.koinapplication.repo.DataRepo
import com.example.koinapplication.repo.DataSource
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.java.KoinJavaComponent.inject
class MainViewModel : ViewModel() {
private val dataRepo by inject(DataSource::class.java)
private var limit = 10
private val _dogListLiveData = MutableLiveData<List<Dog>>()
private var dogList = mutableListOf<Dog>()
val dogListLiveData: MutableLiveData<List<Dog>>
get() = _dogListLiveData
fun searchByBreed(queryText: String) {
dataRepo.searchByBreed(
queryText,
object : DataSource.OnResponseCallback<List<Dog>, String> {
override fun onSuccess(obj: List<Dog>?) {
dogList = mutableListOf()
if (!obj.isNullOrEmpty()) {
dogList.addAll(obj)
dogListLiveData.value = dogList.take(limit)
}
}
override fun onError(error: String) {
Log.i("Calling Network Service", error)
}
})
}
fun loadPaginateBreed(): Boolean {
return if ((limit + 10) < dogList.size) {
limit += 10
Log.i("Pagination new Limit", limit.toString())
dogListLiveData.value = dogList.take(limit)
false
} else {
limit += dogList.size % limit
dogListLiveData.value = dogList.take(limit)
true
}
}
}
these are my modules
#OptIn(KoinApiExtension::class)
val networkModule = module{
factory { AuthInterceptor() }
factory { provideOkHttpClient(get()) }
factory { GranularErrorCallAdapterFactory<Any>() }
single { providesNetworkClient(get(), get()) }
single<DataSource> { DataRepo(get()) }
single <DataSource> { NetworkRepo(get()) }
}
#OptIn(KoinApiExtension::class)
val viewModelModule = module(override = true) {
viewModel { MainViewModel() }
}
and this is my injection code in my fragment and activity
private val myViewModel by sharedViewModel<MainViewModel>()
but it gives me the following error
java.lang.NoSuchMethodError: No virtual method getRootScope()Lorg/koin/core/scope/ScopeDefinition; in class Lorg/koin/core/module/Module; or its super classes (declaration of 'org.koin.core.module.Module' appears in /data/app/com.example.koinapplication-0KW8HWC7rkVfXIFPydc82A==/base.apk)
at com.example.koinapplication.repo.RetrofitServiceKt$viewModelModule$1.invoke(RetrofitService.kt:64)
at com.example.koinapplication.repo.RetrofitServiceKt$viewModelModule$1.invoke(Unknown Source:2)
at org.koin.dsl.ModuleKt.module(Module.kt:31)
at org.koin.dsl.ModuleKt.module$default(Module.kt:29)
at com.example.koinapplication.repo.RetrofitServiceKt.<clinit>(RetrofitService.kt:28)
at com.example.koinapplication.repo.RetrofitServiceKt.getNetworkModule(RetrofitService.kt:17)
at com.example.koinapplication.KoinApplication$onCreate$1.invoke(KoinApplication.kt:15)
at com.example.koinapplication.KoinApplication$onCreate$1.invoke(KoinApplication.kt:9)
at org.koin.core.context.GlobalContext.startKoin$koin_core(GlobalContext.kt:68)
at org.koin.core.context.GlobalContextExtKt.startKoin(GlobalContextExt.kt:32)
at org.koin.core.context.GlobalContextExtKt.startKoin$default(GlobalContextExt.kt:31)
at com.example.koinapplication.KoinApplication.onCreate(KoinApplication.kt:12)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1182)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6460)
at android.app.ActivityThread.access$1300(ActivityThread.java:219)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.L..
The online guide says just declare module and inject but its not happening
how do i get around it?
Any help will be hugely appreciated

kotlin.UninitializedPropertyAccessException: lateinit property roomClickedInterface has not been initialized

I am trying to follow Pusher Chatkit's tutorial for "BUILDING A GROUP CHAT APP USING KOTLIN AND PUSHER CHATKIT" however I am running into an issue with the Recycler View adapter. FYI, I am still learning Kotlin. I've been reading up on lateinit vars but I can't find anything that addresses this case. The error occurs in the recycler view adapter.
This is the error I get:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.android_myneighborsbookshelf, PID: 26692
kotlin.UninitializedPropertyAccessException: lateinit property roomClickedInterface has not been initialized
at com.example.android_myneighborsbookshelf.adapters.ChatRoomsListAdapter.getRoomClickedInterface(ChatRoomsListAdapter.kt:13)
at com.example.android_myneighborsbookshelf.adapters.ChatRoomsListAdapter$ViewHolder.onClick(ChatRoomsListAdapter.kt:66)
at android.view.View.performClick(View.java:6597)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
ChatRoomsListActivity.kt
class ChatRoomsListActivity : AppCompatActivity() {
val adapter = ChatRoomsListAdapter();
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat_room_list)
initRecyclerView()
initChatManager()
}
private fun initRecyclerView() {
recycler_view.layoutManager = LinearLayoutManager(this#ChatRoomsListActivity)
recycler_view.adapter = adapter
}
private fun initChatManager() {
val chatManager = ChatManager(
instanceLocator = "blahblahblah",
userId = "username1-PCKid",
dependencies = AndroidChatkitDependencies(
tokenProvider = ChatkitTokenProvider(
endpoint = "blahblahblah",
// endpoint = "http://10.0.2.2:3000/auth",
userId = "username1-PCKid"
)
)
)
chatManager.connect(listeners = ChatListeners(
onErrorOccurred = { },
onAddedToRoom = { },
onRemovedFromRoom = { },
onCurrentUserReceived = { },
onNewReadCursor = { },
onRoomDeleted = { },
onRoomUpdated = { },
onPresenceChanged = { u, n, p -> },
onUserJoinedRoom = { u, r -> },
onUserLeftRoom = { u, r -> },
onUserStartedTyping = { u, r -> },
onUserStoppedTyping = { u, r -> }
)) { result ->
when (result) {
is Result.Success -> {
// We have connected!
val currentUser = result.value
AppController.currentUser = currentUser
val userJoinedRooms = ArrayList<Room>(currentUser.rooms)
for (i in 0 until userJoinedRooms.size) {
adapter.addRoom(userJoinedRooms[i])
}
currentUser.getJoinableRooms { result ->
when (result) {
is Result.Success -> {
// Do something with List<Room>
val rooms = result.value
runOnUiThread {
for (i in 0 until rooms.size) {
adapter.addRoom(rooms[i])
}
}
}
}
}
adapter.setInterface(object : ChatRoomsListAdapter.RoomClickedInterface {
override fun roomSelected(room: Room) {
if (room.memberUserIds.contains(currentUser.id)) {
// user already belongs to this room
roomJoined(room)
} else {
currentUser.joinRoom(
roomId = room.id,
callback = { result ->
when (result) {
is Result.Success -> {
// Joined the room!
roomJoined(result.value)
}
is Result.Failure -> {
Log.d("TAG", result.error.toString())
}
}
}
)
}
}
})
}
is Result.Failure -> {
// Failure
Log.d("TAG", result.error.toString())
}
}
}
}
private fun roomJoined(room: Room) {
val intent = Intent(this#ChatRoomsListActivity, ChatRoomsListActivity::class.java)
intent.putExtra("room_id", room.id)
intent.putExtra("room_name", room.name)
startActivity(intent)
}
}
RecyclerView Adapter
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.pusher.chatkit.rooms.Room
import android.view.LayoutInflater
import androidx.recyclerview.widget.RecyclerView
import com.example.android_myneighborsbookshelf.R
class ChatRoomsListAdapter: RecyclerView.Adapter<ChatRoomsListAdapter.ViewHolder>() {
private var list = ArrayList<Room>()
lateinit var roomClickedInterface:RoomClickedInterface // <-- Error occurs here - kt.13
fun addRoom(room:Room){
list.add(room)
notifyDataSetChanged()
}
fun setInterface(roomClickedInterface:RoomClickedInterface){
this.roomClickedInterface = roomClickedInterface
}
override fun getItemCount(): Int {
return list.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(
android.R.layout.simple_list_item_1,
parent,
false
)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.roomName.text = list[position].name
}
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView), View.OnClickListener {
override fun onClick(p0: View?) {
roomClickedInterface.roomSelected(list[adapterPosition]) // <-- Error occurs here kt.66
}
var roomName: TextView = itemView.findViewById(android.R.id.text1)
init {
itemView.setOnClickListener(this)
}
}
interface RoomClickedInterface{
fun roomSelected(room:Room)
}
}
Any and all help is appreciated.
lateinit means late initialization. If you do not want to initialize
a variable in the constructor, instead you want to initialize it later
on and if you can guarantee the initialization before using it, then
declare that variable with lateinit keyword. It will not allocate
memory until initialized.
So, You have to initialize the lateinit property before try to use it.
Option - 1: Call setInterface() to initialize the property before click the list item. You can also check whether a lateinit var has already been initialized or not using .isInitialized like below:
override fun onClick(p0: View?) {
if(this::roomClickedInterface.isInitialized) {
roomClickedInterface.roomSelected(list[adapterPosition])
}
}
Option - 2: Remove lateinit property from your adapter and also it's setter
//lateinit var roomClickedInterface:RoomClickedInterface
And pass RoomClickedInterface as parameter of your adapter's constructor
class ChatRoomsListAdapter(val roomClickedInterface:RoomClickedInterface):
RecyclerView.Adapter<ChatRoomsListAdapter.ViewHolder>() {
//Implementation
}
Option - 3: Remove lateinit and use nullable property
private var roomClickedInterface: RoomClickedInterface? = null
And then use like below using null safety notation ?.:
override fun onClick(p0: View?) {
roomClickedInterface?.roomSelected(list[adapterPosition])
}

Coroutines Unit test Mockk java.lang.AbstractMethodError at kotlinx.coroutines.CoroutineContextKt.newCoroutineContext

I want to unit test a method in viewmodal but everytime i failed, and had gone through many of the websites and stack answers but none of them helped out.
I just wanted to test a method in my viewmodal that is loadWeatherForeCast
I had gone through the following links,
Error calling Dispatchers.setMain() in unit test
https://android.jlelse.eu/mastering-coroutines-android-unit-tests-8bc0d082bf15
https://proandroiddev.com/mocking-coroutines-7024073a8c09
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import go_jek.domain.entities.LocationTemperature
import go_jek.domain.interactor.Result
import go_jek.domain.interactor.weatherUseCase.WeatherParam
import go_jek.domain.interactor.weatherUseCase.WeatherUseCase
import go_jek.domain.repository.ApiDataSource
import go_jek.utility.dateUtils.DateUtils
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class WeatherViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#MockK
lateinit var apiDataSource: ApiDataSource //Interface
#MockK
lateinit var dateUtilImpl: DateUtils //Interface
#MockK
lateinit var weatherParam: WeatherParam
#MockK
lateinit var locationTemperatureOutput: LocationTemperature
private lateinit var weatherUseCase: WeatherUseCase
private lateinit var weatherViewModel: WeatherViewModel
#BeforeAll
fun setup() {
MockKAnnotations.init(this)
weatherUseCase = WeatherUseCase(apiDataSource)
weatherViewModel = WeatherViewModel(weatherUseCase, dateUtilImpl, Dispatchers.Unconfined)
}
#Test
fun check() = runBlocking {
every {
weatherUseCase.execute(any(), any(), any())
} answers {
thirdArg<(Result<LocationTemperature>) -> Unit>().invoke(Result.Success(locationTemperatureOutput))
}
weatherViewModel.loadWeatherForeCast(32.45, 72.452)
verify(atLeast = 1) {
apiDataSource.getWeatherInfo(weatherParam)
}
}
}
interface ApiDataSource {
fun getWeatherInfo(weatherParam: WeatherParam): Result<LocationTemperature>
}
class WeatherUseCase(var apiDataSource: ApiDataSource) : UseCase<LocationTemperature, WeatherParam>() {
override suspend fun run(params: WeatherParam): Result<LocationTemperature> = apiDataSource.getWeatherInfo(params)
}
class WeatherParam(
val apiKey: String = BuildConfig.appixu_secretkey,
val location: String
) : UseCase.NoParams()
class LocationTemperature(val location: Location, val current: Current, val forecast: Forecast) : Parcelable
class WeatherViewModel (val weatherUseCase: WeatherUseCase,
val dateUtilImpl: DateUtils,
val uiContext: CoroutineContext = Dispatchers.Main) : ViewModel(), CoroutineScope {
private val job: Job
private val _locationLiveData = MutableLiveData<LocationTemperature>()
val locationLiveData: LiveData<LocationTemperature>
private val _error: MutableLiveData<String> = MutableLiveData()
var error: LiveData<String> = _error
private val loadingState = MutableLiveData<Boolean>()
val loadingLiveData = loadingState
override val coroutineContext: CoroutineContext
get() = uiContext + job
init {
job = Job()
locationLiveData = Transformations.map(_locationLiveData) {
it.apply {
forecast.forecastday.forEach {
it.dayOfWeek = dateUtilImpl.format(it.date, DateUtils.FOR_YYYY_H_MM_H_DD, FULL_DAY)
}
}
}
}
fun handleError(error: Exception) {
loadingState.value = false
_error.value = error.localizedMessage
}
fun loadWeatherForeCast(latitude: Double, longitude: Double) {
val weatherParam = WeatherParam(location = String.format(Locale.getDefault(), "%1f, %2f",
latitude, longitude))
weatherUseCase.execute(this, weatherParam)
{
when (it) {
is Result.Success -> {
loadingState.value = false
_locationLiveData.value = it.data
}
is Result.Error -> {
handleError(it.exception)
}
}
}
}
override fun onCleared() {
job.cancel()
super.onCleared()
}
}
To use mockK with coroutines you need to use the new "coEvery" and "coVerify" functions and it will work better :
https://mockk.io/#coroutines

Categories

Resources