How do I resolve this diamond problem in Kotlin? - android

I have an Interface that I use as the common data source for my RecyclerView Adapters, which looks like this:
interface GenericRVAdapterDataSource {
fun getCellCount() : Int
fun getViewModelForCell(position : Int) : CellViewModel
}
Now, I have two other Interfaces that extend this one:
interface GroupHomeDataSource : GenericRVAdapterDataSource {
fun getJoinedGroupsCount() : Int
fun getJoinedGroupViewModel(forIndex : Int) : GroupHomeCellViewModel
override fun getCellCount(): Int = getJoinedGroupsCount()
override fun getViewModelForCell(position: Int): CellViewModel = getJoinedGroupViewModel(position)
}
and:
interface GroupSuggestedDataSource : GenericRVAdapterDataSource {
fun getSuggestedGroupsCellCount() : Int
fun getSuggestedGroupViewModelForCell(atIndex : Int) : GroupHomeCellViewModel
override fun getCellCount(): Int = getSuggestedGroupsCellCount()
override fun getViewModelForCell(position: Int): CellViewModel = getSuggestedGroupViewModelForCell(position)
}
However, when I implement both interfaces into the class:
class GroupHomeViewModel(app : Application) : AndroidViewModel(app), GroupHomeDataSource, GroupSuggestedDataSource, GroupsHomeInteractionLogic {...}
I got the error:
Class 'GroupHomeViewModel' must override public open fun getCellCount(): Int defined in GroupHomeDataSource because it inherits multiple interface methods of it
For now, I've avoided the problem by just storing both interfaces as variables:
val joinedGroupsDataSource = object: GroupHomeDataSource {
override fun getJoinedGroupsCount(): Int = joinedGroupsList.size
override fun getJoinedGroupViewModel(forIndex: Int): GroupHomeCellViewModel = joinedGroupsList[forIndex]
}
val suggestedGroupsDataSource = object: GroupSuggestedDataSource {
override fun getSuggestedGroupsCellCount(): Int = suggestedGroupsList.size
override fun getSuggestedGroupViewModelForCell(atIndex: Int): GroupHomeCellViewModel = suggestedGroupsList[atIndex]
}
However, I'm not sure that's the most effective way to resolve this diamond problem - if I can even call it that.
Do I just do what the compiler tells me to do and implement getCellCount() and redirect it to one of the interfaces' implementations using:
//MARK:- super interface implementation
override fun getCellCount(): Int {
return super<GroupHomeDataSource>.getCellCount()
//Or: return super<GroupSuggestedDataSource>.getCellCount()
}
override fun getViewModelForCell(position: Int): CellViewModel {
return super<GroupHomeDataSource>.getViewModelForCell(position)
//Or: return super<GroupSuggestedDataSource>.getViewModelForCell(position)
}
//ENDMARK
Or do I implement that method while determining which of the interfaces calls for it (is there a method for this)?

The compiler cannot choose between multiple implementations on its own. But, the whole implementation looks a little overwhelmed. Usually you shouldn't create extended DataSource for each case, use a Generic interface instead. If GroupHomeViewModel provides multiple data sources, just create different properties, as you did.
interface CellViewModel
interface GroupHomeCellViewModel : CellViewModel
interface RVAdapterDataSource<T : CellViewModel> {
fun getCellCount() : Int
fun getViewModelForCell(position : Int) : T
}
class ListAdapterDataSource<T : CellViewModel>(
private val list: List<T>
) : RVAdapterDataSource<T> {
override fun getCellCount() = list.size
override fun getViewModelForCell(forIndex: Int) = list[forIndex]
}
class GroupHomeViewModel(
joinedGroupList: List<GroupHomeCellViewModel>,
suggestedGroupList: List<GroupHomeCellViewModel>
) {
val joinedGroupsDataSource = ListAdapterDataSource(joinedGroupList)
val suggestedGroupsDataSource = ListAdapterDataSource(suggestedGroupList)
}

Related

How should I initialize my interface in a Kotlin class to pass the data to the main activity?

This is my interface:
interface ApiResponseListener {
fun didFetch(response: Response<RandomRecipeApiResponse>)
fun didError(msg: String) }
I want to pass the data into this interface like this:
val apiResponseListener: ApiResponseListener // here the interface should be initialize and I don't know how
apiResponseListener.didError("test")
apiResponseListener.didFetch(response)
This is how you implement an interface you created, https://pl.kotl.in/K5b9dpU5O.
interface ApiResponseListener {
fun didFetch(response: Response<RandomRecipeApiResponse>)
fun didError(msg: String)
}
fun main() {
val apiResponseListener = object : ApiResponseListener {
override fun didFetch(response: Response<RandomRecipeApiResponse>) = Unit
override fun didError(msg: String) { println(msg) }
}
apiResponseListener.didError("test")
apiResponseListener.didFetch(Response())
}
Read more here:
https://kotlinlang.org/docs/interfaces.html
https://kotlinlang.org/docs/object-declarations.html
 
Suggstion
Prefer the keyword on as a prefix in such instances, for example onSuccess(..) and onError(..).

Android Unit testing Mockk argument capture

interface SomeAPIHandler {
fun getUserContent(apiInterface: APIInterface<UserModel>)
}
interface APIInterface<T> {
fun onSuccess(responseModel: T)
fun onError(errorModel: ErrorModel)
}
In my presenter class, it is called like:
apiClient.getUserContent(object : APIInterface<UserModel> {
override fun onSuccess(responseModel: UserModel) = handleSuccess(responseModel)
override fun onError(errorModel: ErrorModel) = handleGetUserModelError()
})
I am getting code coverage issue from SonarQube for this two lines in presentor class:
override fun onSuccess(responseModel: UserModel) = handleSuccess(responseModel)
override fun onError(errorModel: ErrorModel) = handleGetUserModelError()
I am using mockk.io and I think i need to use slot for this. Can someone help how to cover this ?

Generic way to handle all type of responses in Fragment

I have created a Generic Fragment class to handle all type of responses from server. I want to do some sort of DataTableProvider<*> to hanle any type of response.
How could I achieve this.
class TestFragmentActivity : AppCompatActivity(), DataTableProvider<Any> {
protected val mTabPatientsFragment = TabPatientsFragment()
protected val mTabObservationsFragment = TabObservationsFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_fragment)
replaceFragment()
}
private fun replaceFragment(){
supportFragmentManager.beginTransaction().replace(R.id.frame_container,
mTabPatientsFragment).commit()
}
override fun getDataTableListener(mTableFragment: DataTableFragment<Any>): DataTableListener<Any> {
val dataTableId = mTableFragment.dataTableId
if (dataTableId.equals("observations"))
return mTabObservationsFragment
else return mTabPatientsFragment
}
override fun getDataTableConfig(mTableFragment: DataTableFragment<Any>): DataTableConfig {
val dataTableId = mTableFragment.dataTableId
val config = DataTableConfig()
config.noRecordCell = R.layout.cell_no_record
config.showSearch = false
config.showAddButton = false
if (dataTableId.equals("observations"))
{
config.cellResourceId = R.layout.home_observation_cell
} else config.cellResourceId = R.layout.home_patient_cell
return config
}
}
getDataTableListener callback in above fragment has error type mismatch required DataTableListener found TabObservationFragment
TabObservationFragment
class TabObservationFragment : AppBaseFragment(),DataTableListener<Observation>
TabPatientFragment
class TabPatientFragment : AppBaseFragment(),DataTableListener<Patient>
How could I set it to work for all type of responses.
I tried DataTableListener<*> but could not achieve
The error states
projections are not allowed for immediate arguments of a supertype
How could I use DataTableProvider<*> to work for all type of responses
Edit
I have couple of fragment with fragmentViewpager inside TestFragmentActivity .
I have got a structure that helps to implement pagination ,search and implement everything in a fragment. But according to that structure DataTableProvider must be handle in activity and basis of tableId I updated callback of getDataTableListener and getDataTableListener
The above callback should return some type of
Is there a way to achieve callback like below
override fun getDataTableConfig(mTableFragment: DataTableFragment<*>?): DataTableConfig?
override fun getDataTableListener(mTableFragment: DataTableFragment<*>?): DataTableListener<*>?
Quick answer, use "out" modifier:
fun getDataTableListener(mTableFragment: DataTableFragment<Any>): DataTableListener<out Any>
Long answer:
What you are looking for is variance, which can you read about in official kotlin docs:
https://kotlinlang.org/docs/reference/generics.html
Because for example List interface looks like this:
public interface List<out E>
You can do assigement like this:
val list: List<Any> = listOf(1,2,3)
But it is not possible to do:
val mutableList : MutableList<Any> = listOf(1,2,3)
Because MutableList doesn't have "out" modifier. It makes sense, because MutableList can be changed, to MutableList you could add for example String, but it already points to List.
In your example you can use "out" modifier, if, and only if, your DataTableListener doesn't use generic type as input. For example:
interface DataTableListener<T>{
fun assignValue(t:T)
fun getValue():T
}
With interface like above, you still could use "out" modifier in your function, but you won't be able to execute "assignValue" function.
Whole example:
class Patient
class Observation
class DataTableFragment<T>
interface DataTableListener<T> {
fun assignValue(t: T)
fun getValue(): T
}
class TabObservationFragment : DataTableListener<Observation> {
override fun getValue(): Observation {
TODO("Not yet implemented")
}
override fun assignValue(t: Observation) {
TODO("Not yet implemented")
}
}
class TabPatientFragment : DataTableListener<Patient> {
override fun getValue(): Patient {
}
override fun assignValue(t: Patient) {
TODO("Not yet implemented")
}
}
val mTabObservationsFragment = TabObservationFragment()
val mTabPatientsFragment = TabPatientFragment()
fun getDataTableListener(mTableFragment: DataTableFragment<Any>): DataTableListener<out Any> {
val test = "observations"
if (test == "observations")
return mTabObservationsFragment
else return mTabPatientsFragment
}
fun getIt() {
val listener = getDataTableListener(DataTableFragment())
listener.assignValue("test")
}

How to fetch data from JSON in Kotlin Android

I want to fetch some json data, see in the image the green arrow:
The problem is that Android Studio doesn't let me get the data I want. It stops until a step before (I think). In my adapter class check:
holder?.view?.textWeather?.text = weatherFor.weather.toString()
Also it shows me in the emulator the red arrow, what is this?
Below is my main Activity's json method with the classes i want to fetch data for, and the associated Adapter class.
Main Activity
fun fetchJson() {
val url="https://api.openweathermap.org/data/2.5/forecast?q=Prague,CZ&appid=4cf7f6610d941a1ca7583f50e7e41ba3"
val request=Request.Builder().url(url).build()
val client= OkHttpClient()
client.newCall(request).enqueue(object :Callback {
override fun onResponse(call: Call?, response: Response?) {
val body=response?.body()?.string()
println(body)
val gson=GsonBuilder().create()
val forecastfeed=gson.fromJson(body,ForecastFeed::class.java)
runOnUiThread{
recyclerView_main.adapter=MainAdapter(forecastfeed)
}
}
override fun onFailure(call: Call?, e: IOException?) {
println("Failed to execute request")
}
})
}
class ForecastFeed(val list:List<ForecastWeatherList>) { }
class ForecastWeatherList(val weather:List<WeatherData>) { }
class WeatherData(val main:String,val icon:String) { }
Adapter
class MainAdapter(val forecastfeed: ForecastFeed): RecyclerView.Adapter<CustomViewHolder>() {
val forecastWeather = listOf<String>("First","Second")
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val weatherFor = forecastfeed.list.get(position)
holder?.view?.textWeather?.text = weatherFor.weather.toString()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder{
//how do we even create a view
val layoutInflater =LayoutInflater.from(parent?.context)
val cellForRow=layoutInflater.inflate(R.layout.weather_row,parent,false)
return CustomViewHolder(cellForRow)
}
override fun getItemCount(): Int {
return forecastfeed.list.count()
}
}
class CustomViewHolder(val view: View):RecyclerView.ViewHolder(view) { }
You can format the data manually
holder?.view?.textWeather?.text = "weather ${weatherFor.weather.map{it.main}.joinToString(", ")}"
or use data classes
You need to overwrite WeatherData.toString() to have a hand on what's displayed.
class WeatherData(val main:String,val icon:String) {
override fun toString(): String {
return "$main $icon"
}
}
Further more you should use a RecyclerView with a ViewHolder to handle properties one-by-one and enable more complex layouts. If needed.

Instantiating an Interface Listener in Kotlin

I cannot, for the life of me, instantiate an interface outside of a fragment in Kotlin or Kotlin for Android. It was standard procedure in Java to say something like:
MyInterface mInterfaceListener = new MyInterface(this);
mInterfaceListener.invokeSomeGenericMethod();
Note that mInterfaceListener is referring to an Interface, not an onCLickListener or anything like that
How are interfaces instantiated in Kotlin? How do I make a "listener" and trigger an interface's functions?
Below are some attempts in a very simple app I am doing for learning purposes. Notice the variable mPresenterListener which is an Interface
class QuoteActivity : QuoteContract.ViewOps, AppCompatActivity() {
private lateinit var vText: TextView
private lateinit var vFab: FloatingActionButton
private lateinit var mPresenterListener: QuoteContract.PresenterOperations
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mPresenterListener = this.mPresenterListener
vText=findViewById(R.id.main_quote)
vFab=findViewById(R.id.main_fab)
vFab.setOnClickListener{
mPresenterListener.onQuoteRequested()
}
}
override fun displayQuote(quote: String) {
vText.text = quote
}
}
And my presenter:
class QuotePresenter(private val viewListener: QuoteContract.ViewOps): QuoteContract.PresenterOperations {
private lateinit var modelListener: QuoteContract.ModelOperations
init {
modelListener = this.modelListener
}
override fun onQuoteRequested() {
modelListener.generateQuote()
}
override fun onQuoteGenerated(quote: String) {
viewListener.displayQuote(quote)
}
}
The interface:
interface QuoteContract {
//Methods available to Presenter (Presenter -> View)
interface ViewOps{
fun displayQuote(quote: String)
}
//Ops offered from presenter to view (Presenter->View)
interface PresenterOperations {
//Presenter->View
fun onQuoteRequested()
//Presenter->Model
fun onQuoteGenerated(quote: String)
}
//Ops offered from Model to Presenter (Model -> Presenter)
interface ModelOperations {
fun generateQuote()
}
}
You can do watchers/listeners like this:
val textView: TextView = this.findViewById(R.id.amountEdit)
val watcher = object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
val inputAmount = textView.text.toString
val amount = if (!inputAmount.isEmpty()) inputAmount.toDouble() else 0.0
conversionViewModel?.convert(amount)
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
println("before text changed called..")
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
println("text changed..")
}
}
and then:
textView.addTextChangedListener(watcher)
Notice the object in declaring the watcher. Can do the same with a listener.
Also we can use listener without some interface and with default value like:
val someButtonListener: (isChecked: Boolean) -> Unit = {_ -> }
val someButtonListener: (v: View) -> Unit = {_ -> }

Categories

Resources