Котлин: как передать функцию с переменными аргументами в качестве параметра другой функции - PullRequest
2 голосов
/ 26 сентября 2019

Итак, я переписываю код моего приложения, чтобы он был "чистым" (разделение слоев в соответствии с шаблоном MVVM, рекомендованным командой Android)

Здесь у меня есть простой интерфейс Retrofit для взаимодействия с моим 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>
}

Просто простой CRUD.

Теперь, на один уровень выше, у меня есть мой SocialRepository.Чтобы избежать повторения кода, я создал универсальный метод callSafely, который принимает функцию API приостановки и placeId в качестве параметров.

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()
    }
}

Теперь он работает довольно хорошо, конечно, у меня есть и моя активностьи его ViewModel и ViewModel вызывают метод в хранилище и т. д. Это не имеет значения.

Важно то, что для добавления комментария к месту требуются дополнительные данные, такие как фактический текст комментария.Для получения и удаления комментариев требуется только placeId, тогда как при добавлении комментария необходимо указать его содержимое text.Я читал, что передача функций vararg невозможна в Kotlin.Я также не хотел бы загромождать все методы API чем-то вроде List of params, который в большинстве случаев будет пустым и просто создаст путаницу.

Я могу пойти простым путем и просто скопировать кодот callSafely до addPlaceComment и измените его, но это не то, что я ищу.Я знаю, как решить проблему, но я не знаю, как это сделать the clean way.В будущем я мог бы добавить еще несколько конечных точек, требующих дополнительных данных (кроме placeId), и проблема снова появится.

Что бы вы сделали в этой ситуации?Как написать это «правильным способом»?

Я даже не уверен, как правильно выразить то, что я ищу, вот почему этот пост такой бессвязный.Извините за это заранее.Я действительно надеюсь, что вы можете мне помочь.

Ответы [ 2 ]

4 голосов
/ 26 сентября 2019

«Чистый путь» - это очень широкое понятие.Все зависит от ваших потребностей, и нет «единственно хорошего способа сделать что-то».

В вашем конкретном случае у вас есть несколько вариантов:

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) Лямбды (предпочтительно в вашей ситуации)

Никто не заставляет вас использовать ссылки на методы.Используйте лямбды, когда вам это нужно.Но поместите лямбду в качестве последнего параметра для «чистого» кода.

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) }
}
1 голос
/ 26 сентября 2019

Попробуйте это:

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))
}

}

...