Kotlin, reduce duplicated code - kotlin

Every my API service interface class have create static method,
interface AuthApiService {
#FormUrlEncoded
#POST("api/auth/login")
fun postLogin(#Field("username") username: String, #Field("password") password: String):
io.reactivex.Observable<LoginApiResponse>
companion object Factory {
fun create(): AuthApiService {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http:192.168.24.188:8080")
.build()
return retrofit.create(AuthApiService::class.java)
}
}
}
interface BBBApiService {
companion object Factory {
fun create(): BBBApiService {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http:192.168.24.188:8080")
.build()
return retrofit.create(BBBApiService::class.java)
}
}
}
But, I want to define the create() method only once.
So I made the ApiFactory class,
interface ApiFactory {
companion object {
inline fun <reified T>createRetrofit(): T {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http://192.168.24.188:8080")
.build()
return retrofit.create(T::class.java)
}
}
}
interface AuthApiService {
#FormUrlEncoded
#POST("api/auth/login")
fun postLogin(#Field("username") username: String, #Field("password") password: String):
io.reactivex.Observable<LoginApiResponse>
companion object Factory {
fun create(): AuthApiService {
return ApiFactory.createRetrofit()
}
}
But, still, I need to define the create() method in AuthApiService.
Is there any a way implement the ApiFactory class to SubApi classes so that I don't have to define the create method in each child classes?

A simple solution is just to call the function of your ApiFactory directly:
val authApiService = ApiFactory.createRetrofit<AuthApiService>()
But if you want to be able to call AuthApiService.create(), then you can define a marker interface, say, ApiFactoryClient<T>, and mark an empty companion object with it.
interface ApiFactoryClient<T>
interface AuthApiService {
/* ... */
companion object : ApiFactoryClient<AuthApiService>
}
And then make an extension function that works with ApiFactoryClient<T>:
inline fun <reified T> ApiFactoryClient<T>.create(): T = ApiFactory.createRetrofit<T>()
And the usage would be:
val authApiService = AuthApiService.create()

You can modify your ApiFactory like this:
interface ApiFactory {
companion object {
inline fun <reified T>createRetrofit(klass: KClass<T>): T {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http://192.168.24.188:8080")
.build()
return retrofit.create(klass.java)
}
}
}
And then use it to create different service instances:
val authApiService = ApiFactory.createRetrofit(AuthApiService::class)

Related

Is it possible to verify at compile time whether the required function is called for the Factory Class in Kotlin?

class ModelFactory {
fun setA() : ModelFactory {
// blabla...
}
fun setB() : ModelFactory {
// blabla...
}
fun setC() : ModelFactory {
// blabla...
}
fun build() : Model {
// An error occurs if any of setA, setB, and setC is not called.
}
}
//example
fun successTest() {
ModelFactory().setA().setB().setC().build() // No error occurs at compile time
}
fun failTest() {
ModelFactory().setA().build() // An error occurs at compile time because setB and setC are not called.
}
It's awkward grammatically, but I think it's been expressed what I want.
I have already implemented an error-raising runtime for this requirement, but I want to check this at compile time.
If possible, I think I should use annotations. But is this really possible at compile time?
With Kotlin, I have been avoiding builder pattern, as we can always specify default values for non-mandatory fields.
If you still want to use a builder pattern, you can use Step builder pattern that expects all mandatory fields to be set before creating the object. Note that each setter method returns the reference of next setter interface. You can have multiple Step builders based on the combination of mandatory fields.
class Model(val a: String = "", val b: String = "", val c: String = "")
class StepBuilder {
companion object {
fun builder(): AStep = Steps()
}
interface AStep {
fun setA(a: String): BStep
}
interface BStep {
fun setB(b: String): CStep
}
interface CStep {
fun setC(c: String): BuildStep
}
interface BuildStep {
//fun setOptionalField(x: String): BuildStep
fun build(): Model
}
class Steps : AStep, BStep, CStep, BuildStep {
private lateinit var a: String
private lateinit var b: String
private lateinit var c: String
override fun setA(a: String): BStep {
this.a = a
return this
}
override fun setB(b: String): CStep {
this.b = b
return this
}
override fun setC(c: String): BuildStep {
this.c = c
return this
}
override fun build() = Model(a, b , c)
}
}
fun main() {
// cannot build until you call all three setters
val model = StepBuilder.builder().setA("A").setB("B").setC("C").build()
}

Android Kotlin - retrofit2 set POST parameters

interface ApiInterface {
#Headers("Content-Type: application/json")
#POST("testgetmemes/")
fun getMemes(): Call<List<Memes>>
}
object ApiClient {
var BASE_URL:String="https://www.androidisapos.com/"
val getClient: ApiInterface
get() {
val gson = GsonBuilder()
.setLenient()
.create()
val client = OkHttpClient.Builder().build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
return retrofit.create(ApiInterface::class.java)
}
}
and inside a function:
val call: Call<List<Memes>> = ApiClient.getClient.getMemes()
call.enqueue(object : Callback<List<Memes>> {
override fun onResponse(call: Call<List<Memes>>?, response: Response<List<Memes>>) {
setMemes(JSONArray(Gson().toJson(response.body())), gal)
}
override fun onFailure(call: Call<List<Memes>>?, t: Throwable?) {
Log.d(tagg, t!!.toString())
}
})
How can I add POST Parameters (and the values ofc)? I've seen countless examples but they all construct the code of this absolutely awful library differently what makes it impossible to understand when you don't know Kotlin/Java 100%
EDIT:
I tried:
fun getMemes(#Query("test") test: String?): Call<List<Memes>>
and
val call: Call<List<Memes>> = ApiClient.getClient.getMemes("bla")
It doesn't send POST key test with value bla

Dagger 2 with ViewModel, Repository, Room and Coroutines

I' trying to utilize Dagger 2 in a ViewModel + Respository + Room + Retrofit + Coroutines project written in Kotlin.
Currently each ViewModel initializes required repositories and their dependences by itself like so
class HomeViewModel(
application: Application
) : AndroidViewModel(application) {
private val repository: UserRepository =
UserRepository(
Webservice.create(),
AppDatabase.getDatabase(application, viewModelScope).userDao()
)
I would like to get this simplified to this
class HomeViewModel #Inject constructor(
private val repository: UserRepository
): ViewModel()
What I have achieved so far
Created the dagger component and modules
#Singleton
#Component(modules = [
AppModule::class,
NetworkModule::class,
DataModule::class,
RepositoryModule::class
])
interface AppComponent {
val webservice: Webservice
val userRepository: UserRepository
}
#Module
class AppModule(private val app: Application) {
#Provides
#Singleton
fun provideApplication(): Application = app
}
#Module
class DataModule {
#Provides
#Singleton
fun provideApplicationDatabase(app: Application, scope: CoroutineScope) =
AppDatabase.getDatabase(app, scope)
#Provides
#Singleton
fun provideUserDao(db: AppDatabase) = db.userDao()
}
#Module
class NetworkModule {
#Provides
#Singleton
fun provideWebservice() = Webservice.create()
}
#Module
class RepositoryModule {
#Provides
#Singleton
fun provideUserRepository(webservice: Webservice, userDao: UserDao) =
UserRepository(webservice, userDao)
}
Got the AppComponent initilized in the application class
class App : Application() {
companion object {
lateinit var appComponent: AppComponent
}
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
}
private fun initDagger(app: App): AppComponent =
DaggerAppComponent.builder()
.appModule(AppModule(app))
.build()
}
And now I'm stuck.
The first question is: How do I make the ViewModel's inject constructor work?
Originally it was initialized from the HomeFragment like so
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
How do I call the initializer now?
The second question is a bit harder.
The database constructor requies a Coroutine scope in order to prepopulate it in a background thread during creation. How do I pass in a scope now?
Here is the definition of the database and the callback
#Database(
entities = [User::class],
version = 1, exportSchema = false
)
#TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): AppDatabase {
val tempInstance =
INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"database"
)
.fallbackToDestructiveMigration()
.addCallback(AppDatabaseCallback(scope))
.build()
INSTANCE = instance
return instance
}
}
}
private class AppDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch(Dispatchers.IO) {
//inserts
}
}
}
}
}
The second question is a bit harder.
The database constructor requies a Coroutine scope in order to prepopulate it in a background thread during creation. How do I pass in a scope now?
It's actually easier, don't pass in a CoroutineScope, use the GlobalScope for this operation.
The first question is: How do I make the ViewModel's inject constructor work?
You need to obtain the Provider<HomeViewModel> from Dagger, then invoke it inside a ViewModelProvider.Factory to create the instance of HomeViewModel via the provider registered in the Dagger component.
Alternately, if the Activity has its own subcomponent, then you can use #BindsInstance to get the Activity instance into the graph, then move ViewModelProviders.of(activity).get(HomeViewModel::class.java, object: ViewModelProvider.Factory {
...
return homeViewModelProvider.get() as T
...
}) into a module of that subcomponent. Then, from that subcomponent, it would be possible to obtain an actual instance of the HomeViewModel, and still obtain proper scoping + onCleared() callback.
You don't need to pass a coroutine scope just run a coroutine in IO dispacher like:
#Database(
entities = [
Login::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun loginDao(): LoginDao
companion object {
#Volatile private var INSTANCE: AppDatabase? = null
fun getInstance(app: Application): AppDatabase = INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(app).also { INSTANCE = it }
}
private fun buildDatabase(app: Application) =
Room.databaseBuilder(app,
AppDatabase::class.java,
"daily_accountant")
// prepopulate the database after onCreate was called
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// Do database operations through coroutine or any background thread
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught during database creation --> $exception")
}
CoroutineScope(Dispatchers.IO).launch(handler) {
// Pre-populate database operations
}
}
})
.build()
}
}
And remove coroutineScope from from function parameter.

Mockito retrofit2 with MVP architecture

I think somethings wrong about my code in TeamImplsTest, and i need advice :D
This is my code
API interface
interface API {
#GET("lookupteam.php")
fun getTeam(#Query("id") id: String): Call<TeamModel>
}
TeamPresenter
interface MatchPresenter {
fun loadTeamDetail(team_id: String)
}
TeamImpls
class TeamImpls(val teamView: TeamView) : TeamPresenter {
override fun loadTeamDetail(team_id: String) {
val call = RetrofitConfig().getApi().getTeam(team_id)
call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful()) {
val res = response.body()
res?.let { teamView.onSuccess(it) }
}
}
override fun onFailure(call: Call, t: Throwable) {
Log.e("PrevMatchFragment", t.toString())
}
})
}
}
TeamModel
data class TeamModel(
val teams: ArrayList
)
data class TeamModeLResult(
val idTeam: String,
val strTeam: String,
val strAlternate: String,
val strSport: String,
val strStadium: String,
val strTeamBadge: String
)
And
This my TeamImplsTest
class TeamImplsTest {
#Mock
private lateinit var teamView: TeamView
#Mock
private lateinit var teamPresenter: TeamPresenter
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
teamPresenter = TeamImpls(teamView)
}
#Test
fun loadTeamDetail() {
val teams = TeamModel(arrayListOf())
val teamId = "133613"
teamPresenter.loadTeamDetail(teamId)
Mockito.verify(teamView).onSuccess(teams)
}
}
i got error
Wanted but not invoked:
teamView.onSuccess(TeamModel(teams=[]));
-> at com.fathurradhy.matchschedule.domain.presenter.TeamImplsTest.loadTeamDetail(TeamImplsTest.kt:34)
Actually, there were zero interactions with this mock.
Wanted but not invoked:
teamView.onSuccess(TeamModel(teams=[]));
-> at com.fathurradhy.matchschedule.domain.presenter.TeamImplsTest.loadTeamDetail(TeamImplsTest.kt:34)
Actually, there were zero interactions with this mock.
You're not mocking the API call as loadTeamDetail creates its own API instance.
To enable you to test the API call behaviour you could provide the API instance through your constructor, e.g.
class TeamImpls(private val api: API, private val teamView: TeamView) : TeamPresenter {
override fun loadTeamDetail(team_id: String) {
val call = api.getTeam(team_id)
This would then allow you to mock the api behaviour and verify the presenter calls the correct method when the call fails/succeeds, e.g.
class TeamImplsTest {
#Mock
private lateinit var teamView: TeamView
#Mock
private lateinit var api: API
#Mock
private lateinit var teamPresenter: TeamPresenter
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
teamPresenter = TeamImpls(api, teamView)
}
#Test
fun loadTeamDetail() {
val teams = TeamModel(arrayListOf())
val teamId = "133613"
// Use retrofit-mock to create your mockResponse.
// See: https://github.com/square/retrofit/tree/master/retrofit-mock
Mockito.`when`(api.getTeam(teamId)).thenReturn(Calls.response(teams)
teamPresenter.loadTeamDetail(teamId)
Mockito.verify(teamView).onSuccess(teams)
}
}

Android Room + Kotlin pattern

the Android Room documentation says that we should follow the singleton design pattern when instantiating an AppDatabase object.
I was thinking about it, and I would like to know if its recommended to put the AppDatabase class inside my Application class. Or if I can use the Kotlin singleton for that.
Let's say I have a DAO called CarroDAO and class CarrosDatabase that is a RoomDatabase.
Is it ok to create a DatabaseManager class using a Kotlin object/singleton ?
object DatabaseManager {
private var dbInstance: CarrosDatabase
init {
val appContext = MyApplication.getInstance().applicationContext
dbInstance = Room.databaseBuilder(
appContext,
CarrosDatabase::class.java,
"mybd.sqlite")
.build()
}
fun getCarroDAO(): CarroDAO {
return dbInstance.carroDAO()
}
}
So I can get the DAO class like this:
val dao = DatabaseManager.getCarroDAO()
According to Android documentation, we can create a database instance using the singleton design pattern as follows
Create a room database entity
#Entity
data class User(
#PrimaryKey var uid: Int,
#ColumnInfo(name = "first_name") var firstName: String?,
#ColumnInfo(name = "last_name") var lastName: String?
)
Create DAO class
#Dao
interface UserDao {
#Query("SELECT * FROM user")
fun getAll(): List<User>
#Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List<User>
#Query("SELECT * FROM user WHERE first_name LIKE: first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
#Insert
fun insertAll(vararg users: User)
#Delete
fun delete(user: User)
}
Create database with singleton pattern
#Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
#Volatile
private var instance: AppDatabase? = null
fun getInstance(
context: Context
): AppDatabase = instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"database-name"
).build()
}
}
}
You can get database instance by following code
var databaseInstance=AppDatabase.getInstance(context)