I can transform a list with map in one line like this:
override suspend fun getAllTxtFile(): List<TxtFileModel> {
return someDao.getAllTxtFile().map { with(txtFileDataMapper) { it.fromEntityToDomain() } }
}
But I don't know how to do it for single class object:
override suspend fun getTxtFile(txtFileName: String?): TxtFileModel {
val txtFile = someDao.getTxtFile(txtFileName)
val txtFileModel = with(txtFileDataMapper) { txtFile.fromEntityToDomain() }
return txtFileModel
}
So, the difference is that for list I can return a value in one line but for a single object only in 3 lines.
That is not big deal but I wonder if exist some anologue for .map but for a single object. I want something like .mapSingleObject:
override suspend fun getTxtFile(txtFileName: String?): TxtFileModel {
return someDao.getTxtFile(txtFileName).mapSingleObject{ with(txtFileDataMapper) { it.fromEntityToDomain() } }
}
You can use some functions from Kotlin standard library, such as let, run, or with.
If function fromEntityToDomain() is a TxtFile extension function defined within the txtFileDataMapper's class, you can use functions mentioned above:
override suspend fun getTxtFile(txtFileName: String?): TxtFileModel? =
someDao.getTxtFile(txtFileName)?.let {
with(txtFileDataMapper) { it.fromEntityToDomain() }
}
Related
I have this code in my class OutlookItemsAdapter: RecyclerView.Adapter<OutlookItemsViewHolder>():
companion object {
lateinit var outlookItems: SortedList<OutlookItem>
}
init {
outlookItems = SortedList(OutlookItem::class.java, object: SortedListAdapterCallback<OutlookItem>(this){
override fun areItemsTheSame(item1: OutlookItem, item2: OutlookItem): Boolean = item1 == item2
override fun compare(o1: OutlookItem, o2: OutlookItem): Int = o1.DateTime.compareTo(o2.DateTime)
override fun areContentsTheSame(oldItem: OutlookItem, newItem: OutlookItem): Boolean = oldItem.EntryId.equals(newItem.EntryId)
})
}
Where OutlookItem is:
class OutlookItem (
val Subject: String,
val EntryId: String,
val DateTime: LocalDateTime,
val MeetingUrl: String?
)
I need to write a function that receives an EntryId and deletes its equivalent OutlookItem from the list. Unfortunately SortedList doesn't have this capability (i.e. find/remove element determined by e.g. some lambda).
Is there an easy way to accomplish that or do I actually need to implement this finding mechanism on my own?
Since SortedList doesn't actually implement the List interface, you can't use any of Kotlin's helper higher-order functions for Lists on it.
A finding higher-order function can be written like this:
inline fun <T> SortedList<T>.firstOrNull(predicate: (T) -> Boolean): T? {
for (index in 0 until size()){
this[index].let {
if (predicate(it)) return it
}
}
return null
}
Then you could use this to perform your described task like this:
fun SortedList<OutlookItem>.removeByEntryId(entryId: String) {
val item = firstOrNull { it.EntryId == entryId }
if (item != null) remove(item)
}
By the way, by convention, property names should always start with a lower-case letter unless they are constants (in which case they are all caps).
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")
}
So, I'm rewriting my app's code te be "clean" (separation of layers, following MVVM pattern recommended by Android Team)
Here I've got my simple Retrofit interface to communicate with my API
interface Api {
#GET("comments")
suspend fun getPlaceComments(#Query("placeId") placeId: String): Response<List<CommentResponse>>
#POST("comments")
suspend fun addPlaceComment(#Header("placeId") placeId: String, #Header("text") text: String): Response<Unit>
#DELETE("comments")
suspend fun deletePlaceComment(#Header("placeId") placeId: String): Response<Unit>
}
Just a simple CRUD.
Now, one layer up, I've got my SocialRepository. To avoid code repetition, I created generic method callSafely that takes a suspending API function and a placeId as its parameters.
class SocialRepository {
private val client: Api = ApiClient.webservice
private suspend fun <T> callSafely(
apiMethod: suspend (placeId: String) -> Response<T>,
placeId: String,
): T? {
Log.d(TAG, "$apiMethod called safely")
var response: Response<T>? = null
try {
response = apiMethod(placeId)
} catch (e: Exception) {
e.printStackTrace()
}
if (response?.isSuccessful != true) {
Log.w(TAG, "response.isSuccessful isn't true.")
}
return response?.body()
}
suspend fun getPlaceComments(placeId: String): List<CommentResponse>? {
return callSafely(client::getPlaceComments, placeId)
}
suspend fun deletePlaceComment(placeId: String): Unit? {
return callSafely(client::deletePlaceComment, placeId)
}
suspend fun addPlaceComment(placeId: String, text: String): Unit? {
return callSafely(client::addPlaceComment, placeId, text) // HERE LIES THE PROBLEM
// I can't pass additional data because the method signature won't match with what's defined in callSafely()
}
}
Now, it works pretty well, of course I've got also my Activity and its ViewModel and the ViewModel calls a method in the repository etc. It doesn't matter.
What matters is that adding a place comment requires additional data, like, the actual text of the comment. Getting and deleting comments require only placeId, whereas when adding a comment, its content, its text is also required.
I've read that passing vararg functions is impossible in Kotlin. I also wouldn't like to clutter all the API methods with something like a List of params that will most of the time be empty and will just create confusion.
I can go the easy way and just copy the code of callSafely to addPlaceComment and alter it, but that's not what I'm looking for. I know how to solve the problem, but I don't know how to do it the clean way. In the future I might add some more endpoints requiring additional data (except placeId) and the problem will show up again.
What would you do in this situation? How to write it "the correct way"?
I'm not even sure how to properly express what I'm looking for, that's why this post's so rambling. Sorry for that in advance. I really hope you can help me.
The "clean way" is very broad concept. Everything depends on your needs and there is no "The one good way of doing things".
In your particular case you have several options:
1) Typealiases
typealias ApiCall1<P, R> = suspend (P) -> Response<R>
typealias ApiCall2<P1, P2, R> = suspend (P1, P2) -> Response<R>
fun <P> callSafely(param: P, call: ApiCall1<P, YourResult>): YourResult
fun <P1, P2> callSafely(param1: P1, param2: P2, call: ApiCall2<P1, P2, YourResult>): YourResult
2) Varargs
fun callSafely(vararg params: String, call: suspend (arr: Array<String>) -> YourResult {
...
call(*params)
...
}
3) Lambdas (preferable for your situation)
No-one is forcing you to use method references. Use lambdas when you need it. But place the lambda as the last parameter for "cleaner" code.
private suspend fun <T> callSafely(
placeId: String,
apiMethod: suspend (placeId: String) -> Response<T>
): T?
suspend fun getPlaceComments(placeId: String): List<CommentResponse>? {
return callSafely(placeId, client::getPlaceComments)
}
suspend fun deletePlaceComment(placeId: String): Unit? {
return callSafely(placeId, client::deletePlaceComment)
}
suspend fun addPlaceComment(placeId: String, text: String): Unit? {
return callSafely(placeId) { id -> client.addPlaceComment(id, text) }
}
Try this:
class SocialRepository {
private val client: Api = ApiClient.webservice
private suspend fun <T> callSafely(
apiMethod: suspend (placeId: String) -> Response<T>,
vararg stringParams: String,
): T? {
Log.d(TAG, "$apiMethod called safely")
var response: Response<T>? = null
try {
response = apiMethod(stringParams[0])
} catch (e: Exception) {
e.printStackTrace()
}
if (response?.isSuccessful != true) {
Log.w(TAG, "response.isSuccessful isn't true.")
}
return response?.body()
}
suspend fun getPlaceComments(placeId: String): List<CommentResponse>? {
return callSafely(apiMethod= client::getPlaceComments, stringParams=*arrayOf(placeId))
}
suspend fun deletePlaceComment(placeId: String): Unit? {
return callSafely(apiMethod=client::deletePlaceComment, stringParams=*arrayOf(placeId))
}
suspend fun addPlaceComment(placeId: String, text: String): Unit? {
return callSafely(apiMethod = client::addPlaceComment,stringParams= *arrayOf(placeId,text))
}
}
How can I create a class which could be more reusable with enum classes, as I might have few more classes later on? My point is to make it more reusable, flexible and global for other usage.
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object {
private val TAG: String = this::class.java.simpleName
fun fromString(name: String): PaymentMethodType? {
return getEnumFromString(PaymentMethodType::class.java, name)
}
private inline fun <reified T : Enum<T>> getEnumFromString(c: Class<T>?, string: String?): T? {
if (c != null && string != null) {
try {
return enumValueOf<T>(
string.trim()
.toUpperCase(Locale.getDefault()).replace(" ", "_")
)
} catch (e: IllegalArgumentException) {
Log.e(TAG, e.message)
}
}
return null
}
}
}
You can generalize your getEnumFromString function by creating an interface and having your companion object implementing it. An extension on this interface will let you call the function directly on the companion of your enum class.
This will do the trick:
interface EnumWithKey<T : Enum<T>, K> {
val T.key: K
}
/* The reified type parameter lets you call the function without explicitly
* passing the Class-object.
*/
inline fun <reified T : Enum<T>, K> EnumWithKey<T, K>.getByKey(key: K): T? {
return enumValues<T>().find { it.key == key }
}
Now you can create your PaymentMethodType like this:
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object : EnumWithKey<PaymentMethodType, String> {
// Just define what the key is
override val PaymentMethodType.key
get() = type
}
}
And voila, now you can do this:
println(PaymentMethodType.getByKey("Paypal")) // Prints PAYPAL
The EnumWithKey interface can now be reused by just having the companion object of an enum implementing it.
Well? How about this code?
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object {
private val TAG: String = PaymentMethodType::class.simpleName
fun fromString(name: String?): PaymentMethodType? {
val maybeType = PaymentMethodType.values().firstOrNull { it.type == name }
if (maybeType == null) {
Log.e(TAG, "No corresponding PaymentMethodType for $name")
}
return maybeType
}
}
}
Just made getEnumFromString method simpler like this way.
Moreover, if you want to make your PaymentMethodType more "reusable, flexible and global", add some abstract method onto your PaymentMethodType or consider using Sealed class in this case. We can guess that many payment methods require their own protocols, and implementing it by enum requires an externalised when or if-else branch to do so. For example, the code should be looks like this:
fun paymentProcessor(payment: PaymentMethodType): Boolean {
return when (payment) {
PAYPAL -> { processPaypalPayment() }
VISA -> { processVisaPayment() }
// ...
}
}
which is not bad unless numbers of payment methods are limited but not quite desirable. We can remove this insidious if or when keyword like this way(retaining enum class approach):
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal") {
override fun processPayment(): Boolean {
TODO("Not implemented.")
}
},
VISA("Visa") {
override fun processPayment(): Boolean {
TODO("Not implemented.")
}
},
// ... more types ...
;
abstract fun processPayment(): Boolean
// ...
}
With either approach, we can eliminate when keyword in paymentProcessor method I demonstrated like this:
fun paymentProcessor(payment: PaymentMethodType): Boolean {
return payment.processPayment()
}
I don't explain sealed class approach since the code is not much different compare to enum class approach in this case. The official document may help.
Hope this helps.
Get all enum values with PaymentMethodType.values(), then use find() to get the one you need:
fun fromString(type: String): PaymentMethodType? = PaymentMethodType.values().find { it.type.toLowerCase() == type.toLowerCase() }
I want to have an extension function from the RealmList class:
private inline fun RealmList<Any?>.saveAll() {
this.forEach {
item -> Realm.getDefaultInstance().insert(item!! as RealmModel)
}
}
But whenever I use it, this error appears:
To achieve that add out to generic declaration of your extension function.
It would work if such declaration was stated in the RealmList
private inline fun RealmList<out Any?>.saveAll() {
this.forEach {
item -> Realm.getDefaultInstance().insert(item!! as RealmModel)
}
}
Your code is generally unsafe. Please fix your code, read the documentation, stuff like that.
Also, RealmList expects ? extends RealmModel, so you need to use T: RealmModel with out.
fun <T: RealmModel> RealmList<out T?>.saveAll() {
Realm.getDefaultInstance().use { realm ->
val wasInTransaction = realm.isInTransaction()
try {
if(!wasInTransaction) {
realm.beginTransaction()
}
this.forEach {
item -> item?.let { realm.insert(it) }
}
if(!wasInTransaction) {
realm.commitTransaction()
}
} catch(e: Throwable) {
if(realm.isInTransaction()) {
realm.cancelTransaction()
}
}
}
}
Maybe you should use Realms method to insert instead of performing your loop? That way your extension becomes a simple call:
fun <T: RealmModel> RealmList<out T?>.saveAll() = Realm.getDefaultInstance().insert(this)