diff --git a/app/build.gradle b/app/build.gradle index e778a2b..67b8078 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,7 +38,7 @@ dependencies { testCompile 'junit:junit:4.12' // for data binding - kapt 'com.android.databinding:compiler:3.0.0' + kapt 'com.android.databinding:compiler:3.0.1' // for dagger2 compile "com.google.dagger:dagger:2.12" @@ -49,6 +49,14 @@ dependencies { compile "com.github.gfx.android.orma:orma:4.2.5" kapt "com.github.gfx.android.orma:orma-processor:4.2.5" + // for Retrofit2 + compile 'com.squareup.retrofit2:retrofit:2.3.0' + compile 'com.squareup.retrofit2:converter-gson:2.3.0' + compile 'com.squareup.okhttp3:logging-interceptor:3.8.1' + + // RxJava/RxAndroid + compile 'io.reactivex.rxjava2:rxandroid:2.0.1' + compile 'io.reactivex.rxjava2:rxjava:2.1.6' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2e5cb72..e0af0f7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,9 @@ + + + + @@ -17,13 +21,19 @@ + + + + + + - \ No newline at end of file + diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/dao/NameDao.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/dao/NameDao.kt new file mode 100644 index 0000000..ec137d4 --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/dao/NameDao.kt @@ -0,0 +1,25 @@ +package jp.chau2chaun2.kotlindatabindingsample.dao + +import com.github.gfx.android.orma.SingleAssociation +import jp.chau2chaun2.kotlindatabindingsample.model.orma.Name_Relation +import jp.chau2chaun2.kotlindatabindingsample.model.orma.OrmaDatabase +import jp.chau2chaun2.kotlindatabindingsample.model.orma.RandomUser +import javax.inject.Inject + +class NameDao @Inject constructor(private val ormaDatabase: OrmaDatabase) { + + val relation: Name_Relation = ormaDatabase.relationOfName() + + fun insert(randomUser: RandomUser) { + randomUser.results.forEach { result -> + result.serializedName?.let { name -> + relation.inserter().execute(name).also { + name.id = it + result.name = SingleAssociation.just(it) + } + } + } + } + + fun deleteAll(): Int = relation.deleter().execute() +} diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/dao/ResultDao.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/dao/ResultDao.kt new file mode 100644 index 0000000..0ae5aa9 --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/dao/ResultDao.kt @@ -0,0 +1,20 @@ +package jp.chau2chaun2.kotlindatabindingsample.dao + +import jp.chau2chaun2.kotlindatabindingsample.model.orma.OrmaDatabase +import jp.chau2chaun2.kotlindatabindingsample.model.orma.RandomUser +import jp.chau2chaun2.kotlindatabindingsample.model.orma.Result_Relation +import javax.inject.Inject + +/** + * Equals User Dao actually. + */ +class ResultDao @Inject constructor(private val ormaDatabase: OrmaDatabase) { + + val relation: Result_Relation = ormaDatabase.relationOfResult() + + fun insert(randomUser: RandomUser) { + randomUser.results.forEach { result -> + relation.inserter().execute(result).also { result.id = it } + } + } +} diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/ActivityComponent.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/ActivityComponent.kt index 83a49c5..6838ac9 100644 --- a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/ActivityComponent.kt +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/ActivityComponent.kt @@ -4,6 +4,7 @@ import dagger.Subcomponent import jp.chau2chaun2.kotlindatabindingsample.view.BMICalculateActivity import jp.chau2chaun2.kotlindatabindingsample.view.BMIListActivity import jp.chau2chaun2.kotlindatabindingsample.view.BMIRealtimeCalculateActivity +import jp.chau2chaun2.kotlindatabindingsample.view.RandomUserListActivity @ActivityScope @Subcomponent(modules = arrayOf(ActivityModule::class)) @@ -14,4 +15,6 @@ interface ActivityComponent { fun inject(activity: BMICalculateActivity) fun inject(activity: BMIListActivity) + + fun inject(activity: RandomUserListActivity) } diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/AppModule.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/AppModule.kt index 802485f..531c59d 100644 --- a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/AppModule.kt +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/AppModule.kt @@ -7,9 +7,11 @@ import com.github.gfx.android.orma.AccessThreadConstraint import dagger.Module import dagger.Provides import jp.chau2chaun2.kotlindatabindingsample.model.orma.OrmaDatabase +import jp.chau2chaun2.kotlindatabindingsample.net.ApiService +import jp.chau2chaun2.kotlindatabindingsample.net.IApiService import javax.inject.Singleton -@Module +@Module(includes = arrayOf(ClientModule::class)) class AppModule(private val mContext: Context) { @Provides @@ -36,4 +38,10 @@ class AppModule(private val mContext: Context) { .readOnMainThread(AccessThreadConstraint.NONE) .build() } + + @Singleton + @Provides + internal fun providesApiService(): IApiService { + return ApiService().service + } } diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/ClientModule.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/ClientModule.kt new file mode 100644 index 0000000..93d81db --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/di/ClientModule.kt @@ -0,0 +1,11 @@ +package jp.chau2chaun2.kotlindatabindingsample.di + +import dagger.Binds +import dagger.Module +import jp.chau2chaun2.kotlindatabindingsample.net.client.ApiRandomUserClient +import jp.chau2chaun2.kotlindatabindingsample.net.client.IApiRandomUser + +@Module +interface ClientModule { + @Binds fun bindApiService(client: ApiRandomUserClient): IApiRandomUser +} diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/model/orma/RandomUser.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/model/orma/RandomUser.kt new file mode 100644 index 0000000..70df6b4 --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/model/orma/RandomUser.kt @@ -0,0 +1,58 @@ +package jp.chau2chaun2.kotlindatabindingsample.model.orma + +import com.github.gfx.android.orma.SingleAssociation +import com.github.gfx.android.orma.annotation.Column +import com.github.gfx.android.orma.annotation.PrimaryKey +import com.github.gfx.android.orma.annotation.Table +import com.google.gson.annotations.SerializedName + +data class RandomUser(val info: Info, + val results: List) + +data class Info(val seed: String, + val results: Int, + val page: Int, + val version: String) + +@Table("user") +class Result { + + @PrimaryKey + @Transient + var id: Long = 0 + + @Column lateinit var gender: String + + @Column lateinit var email: String + + @Column lateinit var registered: String + + @Column lateinit var dob: String + + @Column lateinit var phone: String + + @Column lateinit var cell: String + + @Column lateinit var nat: String + + @SerializedName("name") + var serializedName: Name? = null + + @Column + @Transient + var name: SingleAssociation = SingleAssociation.just(0) +} + +@Table("name") +class Name { + + @PrimaryKey + @Transient + var id: Long = 0 + + @Column lateinit var first: String + + @Column lateinit var last: String + + @Column lateinit var title: String +} diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/net/ApiService.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/net/ApiService.kt new file mode 100644 index 0000000..6bea720 --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/net/ApiService.kt @@ -0,0 +1,66 @@ +package jp.chau2chaun2.kotlindatabindingsample.net + +import com.google.gson.GsonBuilder +import jp.chau2chaun2.kotlindatabindingsample.model.orma.RandomUser +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Call +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.GET +import java.util.concurrent.TimeUnit + + + +class ApiService { + + // core for controller + val service: IApiService = create(IApiService::class.java) + + lateinit var retrofit: Retrofit + + fun create(serviceClass: Class): S { + val gson = GsonBuilder() + .serializeNulls() + .create() + + // create retrofit + retrofit = Retrofit.Builder() + .addConverterFactory(GsonConverterFactory.create(gson)) + .baseUrl("http://randomuser.me/") + .client(httpBuilder.build()) + .build() + + return retrofit.create(serviceClass) + } + + val httpBuilder: OkHttpClient.Builder get() { + // create http client + val httpClient = OkHttpClient.Builder() + .addInterceptor(Interceptor { chain -> + val original = chain.request() + + //header + val request = original.newBuilder() + .header("Accept", "application/json") + .method(original.method(), original.body()) + .build() + + return@Interceptor chain.proceed(request) + }) + .readTimeout(30, TimeUnit.SECONDS) + + // log interceptor + val loggingInterceptor = HttpLoggingInterceptor() + loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY + httpClient.addInterceptor(loggingInterceptor) + + return httpClient + } +} + +interface IApiService { + @GET("api") + fun apiDemo(): Call +} diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/net/client/ApiRandomUserClient.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/net/client/ApiRandomUserClient.kt new file mode 100644 index 0000000..bb8af8d --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/net/client/ApiRandomUserClient.kt @@ -0,0 +1,56 @@ +package jp.chau2chaun2.kotlindatabindingsample.net.client + +import android.accounts.NetworkErrorException +import io.reactivex.Observable +import io.reactivex.Single +import jp.chau2chaun2.kotlindatabindingsample.model.orma.RandomUser +import jp.chau2chaun2.kotlindatabindingsample.net.IApiService +import javax.inject.Inject + +interface IApiRandomUser { + fun getRandomUser(): Single + fun getRandomUser(numberOfUsers: Int): Observable +} + +class ApiRandomUserClient @Inject constructor(private val apiService: IApiService) : IApiRandomUser { + + override fun getRandomUser(): Single { + return Single.create { subscriber -> + try { + val responce = apiService.apiDemo().execute() + + responce.body()?.takeIf { responce.isSuccessful }?.let { + subscriber.onSuccess(it) + } ?: run { + subscriber.onError(NetworkErrorException("Error occurred somehow.")) + } + + } catch (e: NetworkErrorException) { + subscriber.onError(e) + } + } + } + + override fun getRandomUser(numberOfUsers: Int): Observable { + return Observable.create { subscriber -> + try { + for (i in 0 until numberOfUsers) { + val responce = apiService.apiDemo().execute() + + responce.body()?.takeIf { responce.isSuccessful }?.let { + subscriber.onNext(it) + + } ?: run { + subscriber.onError(NetworkErrorException("Error occurred somehow.")) + return@create + } + } + + subscriber.onComplete() + + } catch (e: NetworkErrorException) { + subscriber.onError(e) + } + } + } +} diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/presenter/RandomUserPresenter.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/presenter/RandomUserPresenter.kt new file mode 100644 index 0000000..91988e3 --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/presenter/RandomUserPresenter.kt @@ -0,0 +1,60 @@ +package jp.chau2chaun2.kotlindatabindingsample.presenter + +import android.content.Context +import android.widget.Toast +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import jp.chau2chaun2.kotlindatabindingsample.dao.NameDao +import jp.chau2chaun2.kotlindatabindingsample.dao.ResultDao +import jp.chau2chaun2.kotlindatabindingsample.model.orma.RandomUser +import jp.chau2chaun2.kotlindatabindingsample.model.orma.Result +import jp.chau2chaun2.kotlindatabindingsample.net.client.IApiRandomUser +import javax.inject.Inject + +class RandomUserPresenter @Inject constructor(private val context: Context, + private val apiRandomUser: IApiRandomUser, + private val resultDao: ResultDao, + private val nameDao: NameDao) { + + fun getCurrentUsers(): List = resultDao.relation.toList() + + fun getNewRandomUser(callback: () -> Unit) { + apiRandomUser.getRandomUser() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ newRandomUser -> + // Successful + registeredRandomUser(newRandomUser) + callback() + + }, { + // Failed + Toast.makeText(context, "Sth Error occurred.", Toast.LENGTH_SHORT).show() + }) + } + + fun getNewRandomUsers(numberOfUsers: Int, callback: () -> Unit) { + apiRandomUser.getRandomUser(numberOfUsers) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ newRandomUser -> + // onNext + registeredRandomUser(newRandomUser) + callback() + + }, { e -> + // onError + Toast.makeText(context, "Sth Error occurred. ${e.message}", Toast.LENGTH_SHORT).show() + }, { + // onComplete + // Nothing to do. + }) + } + + fun deleteAll(): Int = nameDao.deleteAll() + + private fun registeredRandomUser(randomUser: RandomUser) { + nameDao.insert(randomUser) + resultDao.insert(randomUser) + } +} diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/task/RandomUserGetterTask.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/task/RandomUserGetterTask.kt new file mode 100644 index 0000000..4488310 --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/task/RandomUserGetterTask.kt @@ -0,0 +1,17 @@ +package jp.chau2chaun2.kotlindatabindingsample.task + +import android.databinding.ObservableBoolean +import android.util.Log +import jp.chau2chaun2.kotlindatabindingsample.net.client.IApiRandomUser + + +class RandomUserGetterTask(private val apiRandomUser: IApiRandomUser) { + + val loading = ObservableBoolean(false) + + fun execute() { + Thread(Runnable { + Log.e("test", apiRandomUser.getRandomUser().toString()) + }).start() + } +} diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/view/MainActivity.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/view/MainActivity.kt index 0956d25..b646b5a 100644 --- a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/view/MainActivity.kt +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/view/MainActivity.kt @@ -19,4 +19,6 @@ class MainActivity : AppCompatActivity() { fun onClickBMIRealtimeCalculate(view: View) = startActivity(BMIRealtimeCalculateActivity.getIntent(this)) fun onClickBMIList(view: View) = startActivity(BMIListActivity.getIntent(this)) + + fun onClickRandomUserList(view: View) = startActivity(RandomUserListActivity.getIntent(this)) } diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/view/RandomUserListActivity.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/view/RandomUserListActivity.kt new file mode 100644 index 0000000..b096f05 --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/view/RandomUserListActivity.kt @@ -0,0 +1,63 @@ +package jp.chau2chaun2.kotlindatabindingsample.view + +import android.content.Context +import android.content.Intent +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import jp.chau2chaun2.kotlindatabindingsample.R +import jp.chau2chaun2.kotlindatabindingsample.databinding.ActivityUserListBinding +import jp.chau2chaun2.kotlindatabindingsample.view.adapter.RandomUserListAdapter +import jp.chau2chaun2.kotlindatabindingsample.viewmodel.RandomUserListActivityViewModel +import javax.inject.Inject + +class RandomUserListActivity : BaseActivity() { + + @Inject lateinit var adapter: RandomUserListAdapter + + @Inject lateinit var viewModel: RandomUserListActivityViewModel + + private val binding: ActivityUserListBinding by lazy { + DataBindingUtil.setContentView(this, R.layout.activity_user_list) + } + + override fun onActivityInject() { + mComponent.inject(this) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding.list.adapter = this.adapter + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.add_menu, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.add_single -> { + viewModel.addSingle { adapter.notifyDataSetChanged() } + } + + R.id.add_multiple -> { + viewModel.addMultiple { adapter.notifyDataSetChanged() } + } + + R.id.delete_all -> { + viewModel.deleteAll() + adapter.notifyDataSetChanged() + } + } + return super.onOptionsItemSelected(item) + } + + companion object { + fun getIntent(context: Context): Intent { + return Intent(context, RandomUserListActivity::class.java) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/view/adapter/RandomUserListAdapter.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/view/adapter/RandomUserListAdapter.kt new file mode 100644 index 0000000..5490e93 --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/view/adapter/RandomUserListAdapter.kt @@ -0,0 +1,51 @@ +package jp.chau2chaun2.kotlindatabindingsample.view.adapter + +import android.content.Context +import android.databinding.DataBindingUtil +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import jp.chau2chaun2.kotlindatabindingsample.databinding.RowUserListBinding +import jp.chau2chaun2.kotlindatabindingsample.model.orma.Result +import jp.chau2chaun2.kotlindatabindingsample.presenter.RandomUserPresenter +import javax.inject.Inject + +class RandomUserListAdapter @Inject constructor(context: Context, + private val presenter: RandomUserPresenter) : BaseAdapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + + private lateinit var users: List + + init { + updateModel() + } + + private fun updateModel() { + users = presenter.getCurrentUsers() + } + + override fun notifyDataSetChanged() { + updateModel() + super.notifyDataSetChanged() + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val binding = convertView + ?.let { DataBindingUtil.findBinding(convertView) } + ?: RowUserListBinding.inflate(layoutInflater) + + binding.data = getItem(position) + binding.name = getItem(position).name.get() + + return binding.root + } + + override fun getItem(position: Int): Result = users[position] + + override fun getItemId(p0: Int): Long = 0 + + override fun getCount(): Int = users.size + +} diff --git a/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/viewmodel/RandomUserListActivityViewModel.kt b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/viewmodel/RandomUserListActivityViewModel.kt new file mode 100644 index 0000000..8924ca0 --- /dev/null +++ b/app/src/main/java/jp/chau2chaun2/kotlindatabindingsample/viewmodel/RandomUserListActivityViewModel.kt @@ -0,0 +1,49 @@ +package jp.chau2chaun2.kotlindatabindingsample.viewmodel + +import android.content.Context +import android.widget.Toast +import jp.chau2chaun2.kotlindatabindingsample.presenter.RandomUserPresenter +import javax.inject.Inject + +class RandomUserListActivityViewModel @Inject constructor(context: Context, + private val presenter: RandomUserPresenter) { + + val toast: Toast = Toast.makeText(context, "", Toast.LENGTH_LONG) + + var loadingUserCount: Int = 0 + + fun addSingle(callback: () -> Unit) { + loadingUserCount++ + showLoadingToast() + presenter.getNewRandomUser { + --loadingUserCount + showLoadingToast() + callback() + } + } + + fun addMultiple(callback: () -> Unit) { + loadingUserCount += 10 + showLoadingToast() + presenter.getNewRandomUsers(10) { + --loadingUserCount + showLoadingToast() + callback() + } + } + + private fun showLoadingToast() { + if (loadingUserCount > 0) { + toast.setText("New User loading... / ${loadingUserCount} user(s).") + } else { + toast.setText("LOAD COMPLETED") + } + toast.show() + } + + fun deleteAll() { + val recordCount = presenter.deleteAll() + toast.setText("Deleted $recordCount Users") + toast.show() + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index e555712..abb4163 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -23,4 +23,10 @@ android:text="@string/move_to_BMIListActivity" android:onClick="onClickBMIList"/> +