How can i unit test my repository and viewmodel? - android

I want to unit test my viewmodel and repository but I don't know how I can achieve that. I have made a start wit the viewmodeltestclass but I don't know how I can go further and what the best approach is. Should I also test my endpoint class and mainactivity? can someone help me please?
This is my endpoint class:
interface VenuesEndpoint {
#GET("v2/venues/search")
suspend fun get(
#Query("near") city: String,
#Query("limit") limit: String = Constants.LIMIT,
#Query("radius") radius: String = Constants.RADIUS,
#Query("client_id") id: String = Constants.CLIENT_ID,
#Query("client_secret") secret: String = Constants.CLIENT_SECRET,
#Query("v") date: String
): VenuesMainResponse
}
My repository class:
private val _data: MutableLiveData<VenuesMainResponse?> = MutableLiveData(null)
val data: LiveData<VenuesMainResponse?> get() = _data
suspend fun fetch(city: String, date: String) {
val retrofit = ApiClient()
val api = retrofit.retro.create(VenuesEndpoint::class.java)
try {
val response = api.get(
city = city,
date = date
)
_data.value = response
} catch (e: Exception) {
Log.d(TAG, e.message.toString())
_data.value = null
}
}
}
My viewmodel class:
class VenueViewModel() : ViewModel() {
private val repository = VenuesRepository()
val data: LiveData<VenuesMainResponse?> = repository.data
fun getData(city: String, date: String) {
viewModelScope.launch {
repository.fetch(city, date)
}
}
}
My mainActivity class:
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<VenueViewModel>()
private lateinit var adapter: HomeAdapter
private var searchData: List<Venue>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val editText = findViewById<EditText>(R.id.main_search)
viewModel.getData(
city = Constants.CITY,
date = Constants.DATE
)
viewModel.data
.observe(this, Observer {
it.let {
initAdapter()
rv_home.visibility = View.VISIBLE
if (it != null) {
adapter.setData(it.response.venues.sortedBy { myObject -> myObject.name })
searchData = it.response.venues.sortedBy { myObject -> myObject.name }
} else {
Toast.makeText(this, Constants.ERROR_MESSAGE_API, Toast.LENGTH_SHORT).show()
}
}
})
}
My ViewModelTest class:
#RunWith(AndroidJUnit4::class)
class VenueViewModelTest : TestCase() {
private lateinit var viewModel: VenueViewModel
#Before
public override fun setUp() {
super.setUp()
viewModel = VenueViewModel()
}
#Test
fun testVenueViewModel()
{
}
}

Related

Data is not being displayed

So what I am trying to do here is to fetch data from the api by thecocktaildb.com, but I don't get any data shown.
Adapter:
`
class CocktailAdapter(private val cocktails: List<CocktailDetails>) :
RecyclerView.Adapter<CocktailAdapter.CocktailViewHolder>() {
class CocktailViewHolder(val binding: CocktailListItemBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CocktailViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CocktailListItemBinding.inflate(layoutInflater, parent, false)
return CocktailViewHolder(binding)
}
override fun onBindViewHolder(holder: CocktailViewHolder, position: Int) {
val currentCocktail = cocktails[position]
holder.binding.apply {
cocktail = currentCocktail.strDrink
ivLiked.visibility = if (currentCocktail.favourite) View.VISIBLE else View.GONE
Glide
.with(root.context)
.load(currentCocktail.strDrinkThumb)
.centerCrop()
.placeholder(R.drawable.ic_launcher_foreground)
.into(ivImage)
holder.binding.root.setOnClickListener {
(it.context as MainActivity).supportFragmentManager.commit {
val bundle = Bundle()
bundle.putString("cocktail_name", currentCocktail.strDrink)
replace(R.id.container_view, CocktailDetailsFragment::class.java, bundle)
addToBackStack("cocktails_list_to_details")
}
}
}
}
override fun getItemCount(): Int {
return cocktails.size
}
}
`
Dao:
`
#Dao
interface CocktailDao {
#Query("SELECT * FROM cocktails")
suspend fun getCocktails(): List<CocktailDetails>
#Query("SELECT * FROM cocktails WHERE strDrink=:name")
suspend fun getCocktailByName(name: String): CocktailDetails
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertAll(cocktails: List<CocktailDetails>)
#Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(cocktail: CocktailDetails)
}
`
Entity:
`
#Entity(tableName = "cocktails")
data class CocktailDetails(
#PrimaryKey
#ColumnInfo(name = "strDrink") var strDrink: String,
#ColumnInfo(name = "strGlass") var strGlass: String,
#ColumnInfo(name = "strDrinkThumb") var strDrinkThumb: String,
// #ColumnInfo(name = "strIngredient") var strIngredient: String,
// #ColumnInfo(name = "strMeasure") var strMeasure: String,
#ColumnInfo(name = "strInstructions") var strInstructions: String,
#ColumnInfo(name = "favourite") var favourite: Boolean
)
`
AppDatabase:
`
package com.aleks.exam.db
import androidx.room.Database
import androidx.room.RoomDatabase
import com.aleks.exam.db.dao.CocktailDao
import com.aleks.exam.db.entity.CocktailDetails
#Database(entities = [CocktailDetails::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun cocktailDao(): CocktailDao
}
`
ViewModelFactory:
`
class CocktailViewModelFactory(
private val cocktailRepository: CocktailRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CocktailViewModel(cocktailRepository) as T
}
}
`
Fragment:
`
class CocktailDetailsFragment : Fragment() {
private lateinit var binding: FragmentCocktailDetailsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val selectedCocktailName = arguments?.getString("cocktail_name", null)
GlobalScope.launch {
(activity as? MainActivity)?.cocktailViewModel?.getCocktailByName(
selectedCocktailName ?: return#launch
)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCocktailDetailsBinding.inflate(LayoutInflater.from(context))
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeData()
}
private fun observeData() {
GlobalScope.launch {
(activity as? MainActivity)?.cocktailViewModel?.selectedCocktail?.collect {
binding.cocktail = it ?: return#collect
(activity as? MainActivity)?.runOnUiThread {
setDataBinding()
Glide
.with(context ?: return#runOnUiThread)
.load(it.strDrinkThumb)
.centerCrop()
.placeholder(R.drawable.ic_launcher_foreground)
.into(binding.ivImage)
}
}
}
}
private fun setDataBinding() {
binding.cocktail ?: return
if (binding.cocktail?.favourite == true) {
binding.btnLike.setImageResource(android.R.drawable.star_big_on)
} else {
binding.btnLike.setImageResource(android.R.drawable.star_big_off)
}
binding.btnLike.setOnClickListener {
val cocktail = binding.cocktail
cocktail?.favourite = cocktail?.favourite != true
if (cocktail?.favourite == true) {
binding.btnLike.setImageResource(android.R.drawable.star_big_on)
} else {
binding.btnLike.setImageResource(android.R.drawable.star_big_off)
}
GlobalScope.launch {
(activity as? MainActivity)?.cocktailViewModel?.updateFavourites(
cocktail ?: return#launch
)
}
}
}
}
`
Model:
`
data class CocktailDetailResponse (
var strDrinkThumb: String,
var strDrink: String?,
var strGlass: String?,
// var strIngredient:Ingredient,
// var strMeasure:Measure,
var strInstructions:String?
)
//data class Ingredient (
// var strIngredient1:String,
// var strIngredient2:String,
// var strIngredient3:String,
// var strIngredient4:String,
// var strIngredient5:String,
// var strIngredient6:String,
// var strIngredient7:String,
// var strIngredient8:String,
// var strIngredient9:String,
// var strIngredient10:String,
// var strIngredient11:String,
// var strIngredient12:String,
// var strIngredient13:String,
// var strIngredient14:String,
// var strIngredient15:String
//)
//data class Measure(
// var strMeasure1:String,
// var strMeasure2:String,
// var strMeasure3:String,
// var strMeasure4:String,
// var strMeasure5:String,
// var strMeasure6:String,
// var strMeasure7:String,
// var strMeasure8:String,
// var strMeasure9:String,
// var strMeasure10:String,
// var strMeasure11:String,
// var strMeasure12:String,
// var strMeasure13:String,
// var strMeasure14:String,
// var strMeasure15:String
//)
`
Repository:
`
class CocktailRepository constructor(
private val context: Context,
private val cocktailService: CocktailService,
private val cocktailDao: CocktailDao
) {
suspend fun getCocktails(): List<CocktailDetails> {
return try {
if (NetworkUtil.isConnected(context)) {
val cocktails = cocktailService.getCocktails().execute().body()
val roomCocktails = cocktails?.map { mapResponseToDbModel(it) }
cocktailDao.insertAll(roomCocktails ?: return arrayListOf())
}
cocktailDao.getCocktails()
} catch (e: Exception) {
arrayListOf()
}
}
suspend fun getCocktailByName(name: String): CocktailDetails? {
return try {
if (NetworkUtil.isConnected(context)) {
val cocktails = cocktailService.getCocktailByName(name).execute().body()
val roomCocktails = cocktails?.map { mapResponseToDbModel(it) }
cocktailDao.insertAll(roomCocktails ?: return null)
}
return cocktailDao.getCocktailByName(name)
} catch (e: Exception) {
null
}
}
suspend fun updateCocktail(cocktail: CocktailDetails) {
cocktailDao.update(cocktail)
}
private fun mapResponseToDbModel(response: CocktailDetailResponse): CocktailDetails {
return CocktailDetails(
strDrink = response.strDrink ?: "",
strGlass = response.strGlass ?: "",
strDrinkThumb = response.strDrinkThumb,
// strIngredient = response.strIngredient.strIngredient1,
// strMeasure = response.strMeasure.strMeasure1,
strInstructions = response.strInstructions ?: "",
favourite = false
)
}
}
`
Service:
`
interface CocktailService {
#GET("search.php?f=a")
fun getCocktails(): Call<List<CocktailDetailResponse>>
#GET("search.php?s={strDrink}")
fun getCocktailByName(#Path("strDrink") name: String): Call<List<CocktailDetailResponse>>
}
`
NetworkUtil:
`
object NetworkUtil {
fun isConnected(context: Context): Boolean {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val capabilities =
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
if (capabilities != null) {
return if (capabilities.hasTransport(TRANSPORT_CELLULAR)) {
true
} else if (capabilities.hasTransport(TRANSPORT_WIFI)) {
true
} else capabilities.hasTransport(TRANSPORT_ETHERNET)
}
return false
}
}
`
ViewModel:
`
class CocktailViewModel(
private val cocktailRepository: CocktailRepository
) : ViewModel() {
private val cocktailsListStateFlow = MutableStateFlow<List<CocktailDetails>>(arrayListOf())
private val selectedCocktailStateFlow = MutableStateFlow<CocktailDetails?>(null)
val cocktailsList: StateFlow<List<CocktailDetails>> = cocktailsListStateFlow.asStateFlow()
val selectedCocktail: StateFlow<CocktailDetails?> = selectedCocktailStateFlow.asStateFlow()
suspend fun getCocktails() {
val cocktails = cocktailRepository.getCocktails()
cocktailsListStateFlow.value = cocktails
}
suspend fun getCocktailByName(name: String) {
val cocktail = cocktailRepository.getCocktailByName(name)
selectedCocktailStateFlow.value = cocktail ?: return
}
suspend fun updateFavourites(cocktail: CocktailDetails) {
cocktailRepository.updateCocktail(cocktail)
selectedCocktailStateFlow.value =
selectedCocktailStateFlow.value?.copy(favourite = cocktail.favourite)
}
}
`
MainActivity:
`
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var cocktailService: CocktailService
private lateinit var cocktailRepository: CocktailRepository
lateinit var cocktailViewModel: CocktailViewModel
lateinit var db: RoomDatabase
private val retrofit = Retrofit.Builder()
.baseUrl("https://www.thecocktaildb.com/api/json/v1/1/")
.addConverterFactory(GsonConverterFactory.create())
.build()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
init()
observeData()
if (!NetworkUtil.isConnected(this)) {
Snackbar.make(
binding.root,
"No internet connection, information could be outdated",
Snackbar.LENGTH_LONG
).show()
}
GlobalScope.launch {
cocktailViewModel.getCocktails()
}
}
private fun init() {
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java,
"cocktails_database"
).build()
val cocktailDao = (db as AppDatabase).cocktailDao()
cocktailService = retrofit.create(CocktailService::class.java)
cocktailRepository = CocktailRepository(this, cocktailService, cocktailDao)
val cocktailViewModelFactory = CocktailViewModelFactory(cocktailRepository)
cocktailViewModel =
ViewModelProvider(this, cocktailViewModelFactory)[CocktailViewModel::class.java]
}
private fun observeData() {
GlobalScope.launch {
cocktailViewModel.cocktailsList.collect {
runOnUiThread {
val cocktails = it
val sortedCocktails = cocktails.sortedByDescending { it.favourite }
val adapter = CocktailAdapter(sortedCocktails)
binding.cocktailsList.adapter = adapter
binding.tvCocktailsCount.text = "Cocktails: ${it.size}"
}
}
}
}
}
`
I want the Cocktails to be displayed in a list.

Unable to test LiveData inside ViewModel

So, I have this ViewModel class
#HiltViewModel
class HomeViewModel #Inject constructor(private val starWarsRepository: StarWarsRepository) : ViewModel() {
/**
* Two-Way binding variable
*/
val searchQuery: MutableLiveData<String> = MutableLiveData()
val characters = searchQuery.switchMap { query ->
if (query.isNotBlank() && query.isNotEmpty()) {
characterPager.liveData.cachedIn(viewModelScope)
} else {
MutableLiveData()
}
}
fun retrySearch() {
searchQuery.postValue(searchQuery.value)
}
private val characterPager by lazy {
val searchPagingConfig = PagingConfig(pageSize = 20, maxSize = 100, enablePlaceholders = false)
Pager(config = searchPagingConfig) {
CharacterSearchPagingSource(starWarsRepository, searchQuery.value ?: String())
}
}
}
and here it's counter TestingClass
#ExperimentalCoroutinesApi
#RunWith(JUnit4::class)
class HomeViewModelTest {
#get:Rule
val instantTaskExecutionRule = InstantTaskExecutorRule()
private lateinit var homeViewModel: HomeViewModel
private val starWarsAPI = mock(StarWarsAPI::class.java)
private val testDispatcher = UnconfinedTestDispatcher()
#Before
fun setup() {
Dispatchers.setMain(testDispatcher)
homeViewModel = HomeViewModel(StarWarsRepositoryImpl(starWarsAPI, testDispatcher))
}
#AfterTest
fun tearDown() {
Dispatchers.resetMain()
}
#Test
fun `live data should emit success with result when searching for a character`() = runTest {
val character = CharacterEntity("", "", "", "172", "", listOf(), listOf())
`when`(starWarsAPI.searchCharacters("a", null)).thenReturn(CharacterSearchResultEntity(listOf(character, character), null, null))
homeViewModel.searchQuery.value = "a"
val result = homeViewModel.characters.getOrAwaitValue()
assertEquals(PagingSource.LoadResult.Page(listOf(), null, null), result)
}
}
Here is the repository
class StarWarsRepositoryImpl(private val starWarsAPI: StarWarsAPI, private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO) : StarWarsRepository {
override suspend fun searchCharacters(query: String, page: Int?) = withContext(coroutineDispatcher) {
starWarsAPI.searchCharacters(query, page)
}
}
I'm using this extension function from a google sample.
fun <T> LiveData<T>.getOrAwaitValue(time: Long = 2, timeUnit: TimeUnit = TimeUnit.SECONDS, afterObserve: () -> Unit = {}): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this#getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
try {
afterObserve.invoke()
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
} finally {
this.removeObserver(observer)
}
#Suppress("UNCHECKED_CAST")
return data as T
}
The problem is I'm getting an exception from the getOrAwaitValue that LiveData value was never set.?

Android kotlin: i have retrieved data successfully from url and i want to store those data to room database category wise

i want to store name,price,image And productid, category id
model class
#Entity(tableName = "productlisttable")
data class ProductList_Data(
#PrimaryKey
val uid: Int = 0,
#ColumnInfo(name = "_name")
var name: String? = "",
#ColumnInfo(name = "_price")
var price: String? = ""
)
Dao
#Dao
interface ProductListDao {
#Query("SELECT * FROM productlisttable")
fun getAll(): List<ProductList_Data>
#Insert
fun insert(productListData: ProductList_Data)
}
Appdatabase
#Database(entities = arrayOf(ProductList_Data::class), version = 1)
abstract class ProductlistAppDatabase: RoomDatabase() {
abstract fun productListDao(): ProductListDao
}
here's the productlistactivity
class ProductListActivity : AppCompatActivity() {
private val TAG = ProductListActivity::class.java.simpleName
lateinit var imageView: ImageView
lateinit var iVwishls: ImageView
lateinit var iVCart: ImageView
lateinit var productlistAdapter: ProductlistAdapter
lateinit var viewModelJob: Job
lateinit var coroutineScope: CoroutineScope
lateinit var data: String
lateinit var catId: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_product_list)
viewModelJob = Job()
coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Default)
catId = intent.getStringExtra("cat_id").toString()
imageView = findViewById(R.id.imageViewBackFromPluductlist)
iVwishls = findViewById(R.id.imageViewWishls)
iVCart = findViewById(R.id.imageViewCart)
iVwishls.setOnClickListener {
startActivity(
Intent(
applicationContext,
WishlistActivity::class.java
)
)
}
iVCart.setOnClickListener {
startActivity(
Intent(
applicationContext,
CartActivity::class.java
)
)
}
imageView.setOnClickListener { onBackPressed() }
getProductList()
getFromRoom()
}
private fun getFromRoom() {
val db = Room
.databaseBuilder(
applicationContext,
ProductlistAppDatabase::class.java,
"productlistDB"
)
.build()
}
private fun getProductList() {
coroutineScope.launch {
val response =
ServiceApi.retrofitService.getProductList(Rare.getProductList(catId))
if (response.isSuccessful) {
val model = response.body()
productlistAdapter = ProductlistAdapter(this#ProductListActivity, model)
withContext(Dispatchers.Main) {
recyclerviewPlist.layoutManager = LinearLayoutManager(this#ProductListActivity)
recyclerviewPlist.adapter = productlistAdapter
}
} else {
Log.d(TAG, "getProductList: error")
}
}
val db = Room
.databaseBuilder(
applicationContext,
ProductlistAppDatabase::class.java,
"productlistDB"
)
.build()
val data = ProductList_Data(1, "firsrproduct", "100")
db.productListDao().insert(data)
}
}
here's the screenshot of the actual application/as you can see i am able to get data from server like this...
so what i should change or add i tried to define room db and all that bt failed totally

Cannot Pass output from Retrofit enqueue to MainActivity Kotlin

I am calling the API using retrofit in ApiClient Class. Where I am trying to store the output of successful login either message or responseBody into the output string. I tried using the output string as part of ViewModel Observer type too
But I CANNOT pass the value of output from AplClient to MainActivity
APICLIENT
...
package com.example.services.api
lateinit var service: ApiInterface
object ApiClient {
#JvmStatic
private val BASE_URL = GlobalConstants.SWAGGER
//private val sharedPrefClass = SharedPrefClass()
#JvmStatic
private var mApiInterface: ApiInterface? = null
var output = "Initializing"
// val sharedPrefClass: SharedPrefClass? = null
// #JvmStatic
// fun getApiInterface(): ApiInterface {
// return setApiInterface()
// }
#JvmStatic
fun setApiInterface() : String {
val platform = GlobalConstants.PLATFORM
var mAuthToken = GlobalConstants.SESSION_TOKEN
var companyId = GlobalConstants.COMPANY_ID
var phone = phone
var cCode = cCode
//Here a logging interceptor is created
//Here a logging interceptor is created
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
//The logging interceptor will be added to the Http client
//The logging interceptor will be added to the http client
val httpClient = OkHttpClient.Builder()
httpClient.addInterceptor(logging)
//The Retrofit builder will have the client attached, in order to get connection logs
//The Retrofit builder will have the client attached, in order to get connection logs
val retrofit: Retrofit = Retrofit.Builder()
.client(httpClient.build())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
service = retrofit.create<ApiInterface>(ApiInterface::class.java)
val call: Call<Tree> = service.post(phone, cCode, companyId, platform)
var model: Model
call.enqueue(object : Callback<Tree> {
override fun onResponse(
call: Call<Tree>, response: Response<Tree>)
{
Log.e(TAG, "Success")
if (!response.isSuccessful()) {
model = Model("Success", "91", "8884340404")
model.output += response.body().toString()
return;
}
}
override fun onFailure(
call: Call<Tree>,
t: Throwable
) {
Log.e(TAG, "Json/Network Error")
model = Model("Json/Network Error", "91", "8884340404")
model.output = "Json/Network Error"
// handle execution failures like no internet connectivity
}
})
return output
}
}
VIEWMODEL
package com.example.kotlinloginapi.viewModel
import androidx.databinding.Observable
import androidx.databinding.ObservableField
import androidx.lifecycle.MutableLiveData
class Model {
var output:String? = null
var cCode:String? = null
var phone: String? = null
constructor(output: String?, cCode: String?, phone: String?) {
this.output = output
this.cCode = cCode
this.phone = phone
}
}
...
MAINACTIVTY
...
package com.example.kotlinloginapi.ui.login
lateinit var textView: TextView
class MainActivity : AppCompatActivity() {
lateinit var mainBinding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
textView = findViewById<TextView>(R.id.textView)
val mApiService: ApiService<Tree>
val mApiClient = ApiClient
var model: Model
model = Model("Creating View", "91", "8884340404")
model.output = "Creating View"
Observable.just(setApiInterface())
.subscribe {
println(it)
textView!!.append(setApiInterface())}
// mainBinding.viewModel!!.output = "Creating View"
textView.append("\n" + model.output)
/*fun getLoginData(jsonObject : JsonObject?) {
if (jsonObject != null) {
val mApiService = ApiService<JsonObject>()
mApiService.get(
object : ApiResponse<JsonObject> {
override fun onResponse(mResponse : Response<JsonObject>) {
val loginResponse = if (mResponse.body() != null)
finish()
else {
output = mResponse.body().toString()
}
}
override fun onError(mKey : String) {
Toast.makeText(
applicationContext,
"Error",
Toast.LENGTH_LONG
).show()
}
}, ApiClient.getApiInterface().callLogin(jsonObject)
)
}
}*/
}
}
LOGINACTIVITY
package com.example.kotlinloginapi.ui.login
lateinit var cCode: String
lateinit var phone: String
class LoginActivity : AppCompatActivity() {
lateinit var loginBinding : ActivityLoginBinding
lateinit var eTcCode: EditText
lateinit var eTphone: EditText
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loginBinding = DataBindingUtil.setContentView(this, R.layout.activity_login)
eTcCode = findViewById<EditText>(R.id.etcCode)
eTphone = findViewById<EditText>(R.id.etPhone)
val login = findViewById<Button>(R.id.login)
val loading = findViewById<ProgressBar>(R.id.loading)
var model = Model("Binding Model", "91", "8884340404")
loginBinding.model = model
loginBinding.lifecycleOwner
loginViewModel = ViewModelProviders.of(this, LoginViewModelFactory())
.get(LoginViewModel::class.java)
loginViewModel.loginFormState.observe(this#LoginActivity, Observer {
val loginState = it ?: return#Observer
// disable login button unless both username / password is valid
login.isEnabled = loginState.isDataValid
if (loginState.usernameError != null) {
eTcCode.error = getString(loginState.usernameError)
}
if (loginState.passwordError != null) {
eTphone.error = getString(loginState.passwordError)
}
})
loginViewModel.loginResult.observe(this#LoginActivity, Observer {
val loginResult = it ?: return#Observer
loading.visibility = View.VISIBLE
if (loginResult.error != null) {
showLoginFailed(loginResult.error)
}
if (loginResult.success != null) {
updateUiWithUser(loginResult.success)
}
setResult(Activity.RESULT_OK)
//Complete and destroy login activity once successful
finish()
})
eTcCode.afterTextChanged {
loginViewModel.loginDataChanged(
eTcCode.text.toString(),
eTphone.text.toString()
)
}
eTphone.apply {
afterTextChanged {
loginViewModel.loginDataChanged(
eTcCode.text.toString(),
eTphone.text.toString()
)
}
setOnEditorActionListener { _, actionId, _ ->
when (actionId) {
EditorInfo.IME_ACTION_DONE ->
loginViewModel.login(
eTcCode.text.toString(),
eTphone.text.toString()
)
}
false
}
login.setOnClickListener {
loading.visibility = View.VISIBLE
loginViewModel.login(eTcCode.text.toString(), eTphone.text.toString())
}
}
}
private fun updateUiWithUser(model: LoggedInUserView) {
val welcome = getString(R.string.welcome)
val displayName = model.displayName
// TODO : initiate successful logged in experience
cCode = eTcCode.text.toString()
phone = eTphone.text.toString()
var intent = Intent(this, MainActivity::class.java)
startActivity(intent)
Toast.makeText(
applicationContext,
"$welcome $displayName",
Toast.LENGTH_LONG
).show()
}
private fun showLoginFailed(#StringRes errorString: Int) {
Toast.makeText(applicationContext, errorString, Toast.LENGTH_SHORT).show()
}
}
/**
* Extension function to simplify setting an afterTextChanged action to EditText components.
*/
fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) {
this.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
afterTextChanged.invoke(editable.toString())
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
})
}
...
Ideally, I want to use binding observers but I am unable to pass the output of retrofit to MainActivity even without that.
I moved the enqueue call to main activity which solved the problem. Now I can call text views. Will try to achieve this without moving and live data in future.

One-to-many in Room with Kotlin

The task is to open an activity with notes attached to this diary when you select a single diary.
(one-to-many)
This is how entities in the database look like:
#Entity(tableName = "word_table")
data class Word(#ColumnInfo(name = "word") val word: String,
#ColumnInfo(name = "description") val description : String
)
{
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
var id : Long = 0
}
#Entity(tableName = "note_table")
data class Note(#ColumnInfo(name = "note_name") val note : String,
#ColumnInfo(name = "text") val text : String,
#ColumnInfo(name = "diaryId") val diaryId : Long
){
#PrimaryKey(autoGenerate = true)
var idNote : Long = 0
}
Using a data class in NoteRepository.kt
data class NotesAndWords (#Embedded val word : Word,
#Relation(parentColumn = "id", entityColumn = "diaryId")
val notes : List<Note>)
And a Query in WordDao.kt
#Transaction
#Query("SELECT * from word_table ")
fun getSomeNotes() : LiveData<List<NotesAndWords>>
I get the data and save it in the NoteRepository class:
class NoteRepository (private val wordDao : WordDao) {
var allNotes : LiveData<List<NotesAndWords>> = wordDao.getSomeNotes()
suspend fun insertNote(note : Note)
{
wordDao.insertNote(note)
}
}
Then via NoteViewModel.kt passing data to NoteActivity.kt:
class NoteViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NoteRepository
val allNotes: LiveData<List<NotesAndWords>>
init {
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
repository = NoteRepository(wordsDao)
allNotes = repository.allNotes
}
fun insertNote(note: Note) = viewModelScope.launch {
repository.insertNote(note)
}
}
(NoteActivity.kt)
class NoteActivity : AppCompatActivity() {
private val newWordActivityRequestCode = 1
private lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_note)
val adapter = NoteListAdapter(this, intent.getLongExtra("tag", -1) ){
val intent = Intent(this, ClickedActivity::class.java)
intent.putExtra("tag", it.note)
startActivity(intent)
}
recyclerview1.adapter = adapter
recyclerview1.layoutManager = LinearLayoutManager(this)
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
noteViewModel.allNotes.observe(this, Observer {
adapter.setNotes(it)
})
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
val intent = Intent(this, NewWordActivity::class.java)
startActivityForResult(intent, newWordActivityRequestCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK)
{
data?.getStringArrayListExtra(NewWordActivity.EXTRA_REPLY)?.let {
val note = Note(it[0], it[1], intent.getLongExtra("tag", -1))
noteViewModel.insertNote(note)
}
}
else
{
Toast.makeText(applicationContext, R.string.empty_not_saved,
Toast.LENGTH_LONG).show()
}
}
Then, in the adapter, I use MutableMap to transform the list so that the key is the name id and the value is the notes selected on request (attached to a specific diary)
NoteListAdapter.kt:
class NoteListAdapter internal constructor(
context: Context,
val wordId: Long,
private val listener : (Note) -> Unit
) : RecyclerView.Adapter<NoteListAdapter.NoteViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
//private val mContext = context
private var notes = emptyList<NotesAndWords>() // Cached copy of words
private var notesMapped = mutableMapOf<Long, List<Note>>()
inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val noteItemView: TextView = itemView.findViewById(R.id.textView1)
private val noteDescriptionView: TextView = itemView.findViewById(R.id.textView)
fun bindView(note: Note, listener : (Note) -> Unit) {
noteItemView.text = note.diaryId.toString()
noteDescriptionView.text = note.text
itemView.setOnClickListener {
listener(note)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_layout, parent,
false)
return NoteViewHolder(itemView)
}
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
holder.bindView(notesMapped[wordId]!![position], listener)
}
internal fun setNotes(notes: List<NotesAndWords>) {
this.notes = notes
for (i in this.notes) {
notesMapped[i.word.id] = i.notes
}
notifyDataSetChanged()
}
override fun getItemCount() = notesMapped[wordId]!!.size
}
Database:
#Database(entities = [Word::class, Note::class], version = 2, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
private class WordDatabaseCallback(private val scope: CoroutineScope) : RoomDatabase.Callback()
{
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.wordDao())
}
}
}
suspend fun populateDatabase(wordDao: WordDao) {
//wordDao.deleteAll()
//wordDao.deleteAllNotes()
}
}
companion object {
#Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context, scope:CoroutineScope): WordRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
val instance = Room.databaseBuilder(context.applicationContext,
WordRoomDatabase::class.java, "word_database")
.addCallback(WordDatabaseCallback(scope))
//.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
I've created several diaries and one note in each of them, using the buttons to create new diaries and notes. Now, if you select several diaries in turn, then on some attempt to select a diary, a NullPointerException is issued in the adapter, in this line:
override fun getItemCount() = notesMapped[wordId]!!.size
Why is this exception thrown if notesMapped always has the wordId key?
NoteActivity is called from another activity and the diary id is passed to it
This repository on GitHub: https://github.com/Lomank123/RoomDatabase
Edit:
noteViewModel.allNotes.observe(this, Observer {
var getList = emptyList<Note>()
for(i in it)
{
if(i.word.id == wordId)
{
getList = i.notes
break
}
}
adapter.setNotes(getList)
})
I've changed the Observer in NoteActivity and changed setNotes() method in adapter, but now it returns nothing. With for() I get the right notes and give them to adapter.setNotes(). If it doesn't work, how can I get the correct list of notes?
Hi initially the map is empty and the map is returning a null value and you are checking size on a null object.
Also as a good practice do not use a map instead use a list of notes only and pass the list directly.

Categories

Resources