diff --git a/app/build.gradle b/app/build.gradle index bed1188..6312278 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,6 +27,7 @@ android { dependencies { def ktx_version = "2.0.0" + def kotlin_coroutines_version = "1.0.1" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -39,6 +40,10 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$ktx_version" implementation project (':ktandroidarchitecturecore') testImplementation 'junit:junit:4.12' + + implementation 'androidx.paging:paging-runtime-ktx:2.1.0' + + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/DIUtils.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/DIUtils.kt new file mode 100644 index 0000000..9073e8e --- /dev/null +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/DIUtils.kt @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sysdata.kt.ktandroidarchitecture + +import com.sysdata.kt.ktandroidarchitecture.ui.Note +import it.sysdata.ktandroidarchitecturecore.interactor.Channel + +class DIUtils { + + private object Holder { + val INSTANCE = Channel() + } + + companion object { + val channel: Channel by lazy { + Holder.INSTANCE + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/MainApplication.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/MainApplication.kt index 0657436..08e8c96 100644 --- a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/MainApplication.kt +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/MainApplication.kt @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.sysdata.kt.ktandroidarchitecture import android.app.Application diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/MainApplicationConfig.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/MainApplicationConfig.kt index 9893fcc..33bee5f 100644 --- a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/MainApplicationConfig.kt +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/MainApplicationConfig.kt @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.sysdata.kt.ktandroidarchitecture class MainApplicationConfig private constructor() { diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/AuthRepository.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/AuthRepository.kt index ce8f808..8640fe8 100644 --- a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/AuthRepository.kt +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/AuthRepository.kt @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.sysdata.kt.ktandroidarchitecture.repository import com.sysdata.kt.ktandroidarchitecture.repository.model.UserLogged @@ -5,6 +20,9 @@ import it.sysdata.ktandroidarchitecturecore.BaseRepository import it.sysdata.ktandroidarchitecturecore.exception.Failure import it.sysdata.ktandroidarchitecturecore.functional.Either +/** + * Mock implementation of a repository + */ class AuthRepository:BaseRepository() { private object Holder { diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/model/UIUserLogged.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/model/UIUserLogged.kt index 523edac..0f3d9fc 100644 --- a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/model/UIUserLogged.kt +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/model/UIUserLogged.kt @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.sysdata.kt.ktandroidarchitecture.repository.model diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/model/UserLogged.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/model/UserLogged.kt index 7de5764..3d6dd5b 100644 --- a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/model/UserLogged.kt +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/repository/model/UserLogged.kt @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.sysdata.kt.ktandroidarchitecture.repository.model diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/LoginActivity.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/LoginActivity.kt index 30f20c4..14ea2f8 100644 --- a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/LoginActivity.kt +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/LoginActivity.kt @@ -1,17 +1,33 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.sysdata.kt.ktandroidarchitecture.ui -import android.app.Activity -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProviders import android.os.Bundle + import android.text.Editable import android.text.TextWatcher import android.view.View import android.widget.Toast import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.ViewModelProviders +import androidx.paging.PagedList +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.sysdata.kt.ktandroidarchitecture.R import com.sysdata.kt.ktandroidarchitecture.repository.model.UIUserLogged -import com.sysdata.kt.ktandroidarchitecture.repository.model.UserLogged import com.sysdata.kt.ktandroidarchitecture.usecase.LoginActionParams import com.sysdata.kt.ktandroidarchitecture.viewmodel.LoginViewModel import it.sysdata.ktandroidarchitecturecore.exception.Failure @@ -20,6 +36,7 @@ import kotlinx.android.synthetic.main.activity_login.* class LoginActivity : FragmentActivity(), View.OnClickListener, TextWatcher { private var viewModel : LoginViewModel? = null + private var adapter: PagedListAdapterImpl? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -37,6 +54,41 @@ class LoginActivity : FragmentActivity(), View.OnClickListener, TextWatcher { viewModel?.actionLogin?.observe(this, ::onUserLoggged) viewModel?.actionLogin?.observeFailure(this, ::onLoginFailed) + + // initalization of the datasource channel only with the page size because we use a test datasource that don't need initial datas + viewModel?.channelNotes?.initDatasource(pageSize = 10) + + // init of the adapter with a click listener for the items + adapter = PagedListAdapterImpl { + viewModel?.channelPostNotes?.postData(it) + } + + recycler_view.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) + recycler_view.adapter = adapter + + // we observe channel notes to have the updates from the adapter that sends the datas through livedata + viewModel?.channelNotes?.observe(this,::onPostNote) + // we observe a general channel used to send datas not related to the adapter + viewModel?.channelPostNotes?.observe(this, ::onReceivePost) + } + + /** + * This method recieves the datas from the channel not related to the adapter of the paged list + * @param note Note? + */ + private fun onReceivePost(note: Note?) { + Toast.makeText(this, "note : $note", Toast.LENGTH_SHORT).show() + } + + /** + * this method update the adapter with the the list recieved by the livedata + * + * @param list PagedList? + */ + private fun onPostNote(list: PagedList?) { + list?.let { + adapter?.submitList(list) + } } private fun onLoginFailed(failure: Failure?) { diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/Note.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/Note.kt new file mode 100644 index 0000000..0d43141 --- /dev/null +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/Note.kt @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sysdata.kt.ktandroidarchitecture.ui + +import androidx.recyclerview.widget.DiffUtil +import java.util.* + +/** + * Data object used into the paged lisi adapter, we implement a [diff callback][DiffUtil.ItemCallback] because is need by the adapter + * + * @property index Int + * @property noteId String + * @property title String + * @property content String + * @constructor + */ +data class Note(val index:Int, val noteId: String = UUID.randomUUID().toString()) { + companion object { + val DiffCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Note, newItem: Note): Boolean + = oldItem.noteId == newItem.noteId + + override fun areContentsTheSame(oldItem: Note, newItem: Note): Boolean + = oldItem.noteId == newItem.noteId && oldItem.title == newItem.title && oldItem.content == newItem.content + } + } + var title: String = "" + var content: String = "" +} \ No newline at end of file diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/NoteDataSource.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/NoteDataSource.kt new file mode 100644 index 0000000..b9ed5c2 --- /dev/null +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/NoteDataSource.kt @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sysdata.kt.ktandroidarchitecture.ui + +import it.sysdata.ktandroidarchitecturecore.BasePositionalDatasource + +/** + * Implementation of [BasePositionalDatasource] with notes + */ +class NoteDataSource: BasePositionalDatasource() \ No newline at end of file diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/PagedListAdapterImpl.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/PagedListAdapterImpl.kt new file mode 100644 index 0000000..d437876 --- /dev/null +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/PagedListAdapterImpl.kt @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sysdata.kt.ktandroidarchitecture.ui + +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.paging.PagedListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.sysdata.kt.ktandroidarchitecture.R + +/** + * Dummy implementation of [PagedListAdapter] + * + * @property onClick Function1 + * @constructor + */ +class PagedListAdapterImpl(val onClick: (Note) -> Unit) : PagedListAdapter(Note.DiffCallback) { + class ListItemViewHolder(val view: View): RecyclerView.ViewHolder(view) { + var note: Note? = null + set(value) { + if(value != null){ + val tv: TextView = view.findViewById(R.id.itemText) + tv.text = value.toString() + } + field = value + } + } + + companion object { + private val TAG = this::class.java.simpleName + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListItemViewHolder + = ListItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.note_list_item, parent, false)) + + override fun onBindViewHolder(holder: ListItemViewHolder, position: Int) { + Log.d(TAG, "Binding view holder at position $position") + holder.note = getItem(position) + holder.view.setOnClickListener { onClick(holder.note!!) } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/TestDataSource.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/TestDataSource.kt new file mode 100644 index 0000000..9f895ed --- /dev/null +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/ui/TestDataSource.kt @@ -0,0 +1,78 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sysdata.kt.ktandroidarchitecture.ui + +import androidx.paging.ItemKeyedDataSource + +/** + * Fake datasource that returns random data on each call + * + * @property datas List + */ +class TestDataSource: ItemKeyedDataSource(){ + + private val datas = listOf(Note(1), Note(2), Note(3), Note(4), Note(5)) + + override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { + val requestedLoadSize = params.requestedLoadSize + val requestedInitialKey = params.requestedInitialKey ?: 0 + + val initItem = datas.firstOrNull { it.index == requestedInitialKey } + val initIndex = initItem?.let {datas.indexOf(initItem)} ?: 0 + val newData = getSubList(initIndex, initIndex + requestedLoadSize) + callback.onResult(newData) + } + + private fun getSubList(initIndex: Int, finalIndex: Int): MutableList { + val newData = mutableListOf() + for (i in initIndex until finalIndex) { + newData.add(datas[i % datas.size]) + } + return newData + } + + override fun loadAfter(params: LoadParams, callback: LoadCallback) { + val key = params.key + val requestedLoadSize = params.requestedLoadSize + + val initItem = datas.firstOrNull { it.index == key } + val initIndex = initItem?.let {datas.indexOf(initItem)} ?: 0 + val newData = getSubList(initIndex, initIndex + requestedLoadSize) + callback.onResult(newData) + } + + override fun loadBefore(params: LoadParams, callback: LoadCallback) { + val key = params.key + val requestedLoadSize = params.requestedLoadSize + + val initItem = datas.firstOrNull { it.index == key } + var initIndex = initItem?.let {datas.indexOf(initItem)} ?: 0 + var finalIndex = initIndex + if(initIndex - requestedLoadSize >= 0){ + initIndex -= requestedLoadSize + } else { + initIndex = 0 + finalIndex = initIndex + requestedLoadSize + } + val newData = getSubList(initIndex, finalIndex) + callback.onResult(newData) + } + + override fun getKey(item: Note): Int { + return item.index + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/usecase/LoginUseCase.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/usecase/LoginUseCase.kt index bda8315..f93950f 100644 --- a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/usecase/LoginUseCase.kt +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/usecase/LoginUseCase.kt @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.sysdata.kt.ktandroidarchitecture.usecase import com.sysdata.kt.ktandroidarchitecture.repository.AuthRepository @@ -6,6 +21,9 @@ import it.sysdata.ktandroidarchitecturecore.exception.Failure import it.sysdata.ktandroidarchitecturecore.functional.Either import it.sysdata.ktandroidarchitecturecore.interactor.UseCase +/** + * Fake login use case that calls a mock repository + */ class LoginUseCase: UseCase() { override suspend fun run(params: LoginActionParams): Either { return AuthRepository.instance.login(params.email, params.password) diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/usecase/Params.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/usecase/Params.kt index 4363f5d..73d1bc1 100644 --- a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/usecase/Params.kt +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/usecase/Params.kt @@ -1,3 +1,18 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.sysdata.kt.ktandroidarchitecture.usecase import it.sysdata.ktandroidarchitecturecore.interactor.ActionParams diff --git a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/viewmodel/LoginViewModel.kt b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/viewmodel/LoginViewModel.kt index fb17b41..0b5a34c 100644 --- a/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/viewmodel/LoginViewModel.kt +++ b/app/src/main/java/com/sysdata/kt/ktandroidarchitecture/viewmodel/LoginViewModel.kt @@ -1,15 +1,42 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.sysdata.kt.ktandroidarchitecture.viewmodel +import com.sysdata.kt.ktandroidarchitecture.DIUtils import com.sysdata.kt.ktandroidarchitecture.repository.model.UIUserLogged import com.sysdata.kt.ktandroidarchitecture.repository.model.UserLogged +import com.sysdata.kt.ktandroidarchitecture.ui.Note +import com.sysdata.kt.ktandroidarchitecture.ui.TestDataSource import com.sysdata.kt.ktandroidarchitecture.usecase.LoginActionParams import com.sysdata.kt.ktandroidarchitecture.usecase.LoginUseCase -import it.sysdata.ktandroidarchitecturecore.interactor.Action +import it.sysdata.ktandroidarchitecturecore.interactor.* import it.sysdata.ktandroidarchitecturecore.platform.BaseViewModel class LoginViewModel: BaseViewModel() { + //action to launch a usecase that simulate a login val actionLogin = Action.Builder() .useCase(LoginUseCase::class.java) .buildWithUiModel { UIUserLogged(it.username) } + + // definition of the datasource channel, used by the paged list + val channelNotes = DataSourceChannel.Builder() + .dataSource(TestDataSource::class.java) + .build() + + // base channel used to post and recieve datas + val channelPostNotes = DIUtils.channel } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 2cf593e..b694922 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,6 +1,8 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/note_list_item.xml b/app/src/main/res/layout/note_list_item.xml new file mode 100644 index 0000000..08ce614 --- /dev/null +++ b/app/src/main/res/layout/note_list_item.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/ktandroidarchitecturecore/build.gradle b/ktandroidarchitecturecore/build.gradle index fb1733c..515b9f3 100644 --- a/ktandroidarchitecturecore/build.gradle +++ b/ktandroidarchitecturecore/build.gradle @@ -64,6 +64,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.paging:paging-runtime-ktx:2.1.0' } repositories { diff --git a/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/BaseItemKeyedDatasource.kt b/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/BaseItemKeyedDatasource.kt new file mode 100644 index 0000000..e3d9d70 --- /dev/null +++ b/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/BaseItemKeyedDatasource.kt @@ -0,0 +1,140 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package it.sysdata.ktandroidarchitecturecore + +import androidx.paging.* + +/** + * A simple implementation of [ItemKeyedDataSource] + * + * @param Key:Any, the key to get the item from the datasource + * @param Data : Any, the data returned on each call + * @property defaultLoadSize Int?, the default size of a page + * @property datas List, the initial dataset + */ +open class BaseItemKeyedDatasource: ItemKeyedDataSource(){ + + private var defaultLoadSize: Int? = null + protected lateinit var datas: List + + /** + * This method if called to load all the data after the position of the item with the given key + * + * @param params LoadParams + * @param callback LoadCallback + */ + override fun loadAfter(params: LoadParams, callback: LoadCallback) { + + val requestedLoadSize = params.requestedLoadSize + val key = params.key + + val item = getItemForKey(key) + var startPosition = datas.indexOf(item) + startPosition = if(startPosition < datas.size) startPosition else 0 + + val subList = getSubList(startPosition, requestedLoadSize) + + callback.onResult(subList) + } + + /** + * This method if called to load all the data before the position of the item with the given key + * + * @param params LoadParams + * @param callback LoadCallback + */ + override fun loadBefore(params: LoadParams, callback: LoadCallback) { + val requestedLoadSize = params.requestedLoadSize + val key = params.key + + val item = getItemForKey(key) + var startPosition = datas.indexOf(item) - requestedLoadSize + startPosition = if(startPosition > 0) startPosition else 0 + + val subList = getSubList(startPosition, requestedLoadSize) + + callback.onResult(subList) + } + + /** + * This method returns the key of the given item + * + * @param item Data + * @return Key + */ + override fun getKey(item: Data): Key { + return item.hashCode() as Key + } + + /** + * This method is used to load the initial datas + * @param params LoadInitialParams + * @param callback LoadInitialCallback + */ + override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { + val placeholdersEnabled = params.placeholdersEnabled + val requestedInitialKey = params.requestedInitialKey + var requestedLoadSize = params.requestedLoadSize + + val item = requestedInitialKey?.let { getItemForKey(it) } + var startPosition = item?.let {datas.indexOf(item)} ?: 0 + startPosition = if(startPosition < datas.size) startPosition else 0 + + defaultLoadSize?.let { + if(requestedLoadSize > it){ + requestedLoadSize = it + } + } + val subList = getSubList(startPosition, requestedLoadSize) + + if (placeholdersEnabled) { + callback.onResult(subList, startPosition, datas.size) + } else { + callback.onResult(subList) + } + } + + /** + * This method is used to retrieve the item based on the key, + * Override this method to use another key + * + * @param key Key + * @return Data + */ + protected open fun getItemForKey(key: Key):Data{ + return datas.first { it.hashCode() == key.hashCode() } + } + + /** + * returns a sublist of the initial dataset + * + * @param startPosition Int + * @param defaultLoadSize Int + * @return List + */ + private fun getSubList(startPosition: Int, defaultLoadSize: Int): List { + if(startPosition >= datas.size){ + return datas.subList(datas.size - 1, datas.size - 1) + } + val endIndex = if (startPosition + defaultLoadSize <= datas.size) startPosition + defaultLoadSize else datas.size - 1 + return datas.subList(startPosition, endIndex) + } + + fun init(datas: List, pageSize: Int) { + this.datas = datas + this.defaultLoadSize = pageSize + } +} \ No newline at end of file diff --git a/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/BasePositionalDatasource.kt b/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/BasePositionalDatasource.kt new file mode 100644 index 0000000..060d103 --- /dev/null +++ b/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/BasePositionalDatasource.kt @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2019 Sysdata S.p.a. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package it.sysdata.ktandroidarchitecturecore + +import androidx.paging.PositionalDataSource + +/** + * A simple implementation of [PositionalDataSource] + * + * @param Data : the type of data given by the DataSource + * @property defaultLoadSize Int?, default size of a page + * @property datas List, initial dataset + * @property currentPosition Int, position of the first item we want + */ +open class BasePositionalDatasource: PositionalDataSource(){ + + private var defaultLoadSize: Int? = null + private lateinit var datas: List + private var currentPosition: Int = 0 + + /** + * This method returns the datas in a given range of positions + * + * @param params LoadRangeParams + * @param callback LoadRangeCallback + */ + override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback) { + val loadSize = params.loadSize + val startPosition = params.startPosition + + val subList = if(currentPosition > 0 && defaultLoadSize != null){ + getSubList(currentPosition, defaultLoadSize as Int) + } else{ + getSubList(startPosition, loadSize) + } + + callback.onResult(subList) + } + + /** + * This method loads the initial datas + * + * @param params LoadInitialParams + * @param callback LoadInitialCallback + */ + override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { + var startPosition = params.requestedStartPosition + var pageSize = params.pageSize + val placeholdersEnabled = params.placeholdersEnabled + val requestedLoadSize = params.requestedLoadSize + + startPosition = if(startPosition < datas.size) startPosition else 0 + + defaultLoadSize?.let { + if(pageSize > it){ + pageSize = it + } + } + val subList = getSubList(startPosition, requestedLoadSize) + if(placeholdersEnabled){ + callback.onResult(subList, startPosition, datas.size) + } else { + callback.onResult(subList, startPosition) + } + } + + /** + * returns a sublist of the dataset + * + * @param startPosition Int + * @param defaultLoadSize Int + * @return List + */ + private fun getSubList(startPosition: Int, defaultLoadSize: Int): List { + if(startPosition >= datas.size){ + return datas.subList(datas.size - 1, datas.size - 1) + } + val endIndex = if (startPosition + defaultLoadSize <= datas.size) startPosition + defaultLoadSize else datas.size - 1 + currentPosition = endIndex + return datas.subList(startPosition, endIndex) + } + + fun init(datas: List, pageSize: Int) { + this.datas = datas + this.defaultLoadSize = pageSize + } +} \ No newline at end of file diff --git a/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/interactor/Channel.kt b/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/interactor/Channel.kt new file mode 100644 index 0000000..0067627 --- /dev/null +++ b/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/interactor/Channel.kt @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2018 Sysdata Digital, S.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package it.sysdata.ktandroidarchitecturecore.interactor + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer + +/** + * [Channel] allow to observe a [MutableLiveData] used to post datas time by time + * + */ +open class Channel{ + + var liveData: LiveData = MutableLiveData() + + /** + * Define the function that will use for handle the data posted by the channel + * + * @param owner for [liveData] + * @param body the function that will use for handle the data posted by the channel + */ + fun observe(owner: LifecycleOwner, body: (Data?) -> Unit) { + liveData.observe(owner, Observer(body)) + } + + /** + * Use this method to post datas through channel + * + * @param data Data to be posted on the livedata + */ + fun postData(data: Data){ + val postLiveData = liveData + if (postLiveData is MutableLiveData) { + postLiveData.postValue(data) + } + } + +} + + diff --git a/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/interactor/DataSourceChannel.kt b/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/interactor/DataSourceChannel.kt new file mode 100644 index 0000000..a1618aa --- /dev/null +++ b/ktandroidarchitecturecore/src/main/java/it/sysdata/ktandroidarchitecturecore/interactor/DataSourceChannel.kt @@ -0,0 +1,111 @@ +/** + * Copyright (C) 2018 Sysdata Digital, S.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package it.sysdata.ktandroidarchitecturecore.interactor + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.paging.DataSource +import androidx.paging.LivePagedListBuilder +import androidx.paging.PagedList +import it.sysdata.ktandroidarchitecturecore.BaseItemKeyedDatasource +import it.sysdata.ktandroidarchitecturecore.BasePositionalDatasource + +/** + * [DataSourceChannel] allow to define [Datasource] and [MutableLiveData] for handle the success, loading and failure case + * + * + */ +class DataSourceChannel: Channel>(){ + + private lateinit var dataSource: DataSource + + /** + * + * This method is used to initialize the datasource associated with the channel + * and the live data for the paged lists associated with the datasource + * + * @param datas List, list of datas used as dataset of the datasource, + * if your datasource doesn't need an initial dataset don't set this parameter and set the pagesize + * @param pageSize Int, size of the page to load + * @param initialLoadSizeHint Int, size of the initial page + * @param placeHolderEnabled Boolean + */ + fun initDatasource(datas: List? = null, pageSize: Int = (datas?.size ?: 0) / 4, initialLoadSizeHint: Int = pageSize * 2, placeHolderEnabled: Boolean = false){ + val dataSourceBase = dataSource + this.liveData = initLiveData(dataSource, pageSize, initialLoadSizeHint, placeHolderEnabled) + if (datas != null && datas.isNotEmpty()) { + if (dataSourceBase is BasePositionalDatasource) { + dataSourceBase.init(datas, pageSize) + } else if(dataSourceBase is BaseItemKeyedDatasource){ + dataSourceBase.init(datas, pageSize) + } + } + } + + /** + * this method is used to create a livedata of paged list based on the channel's datasource + * + * @param dataSource DataSource + * @param pageSize Int + * @param initialLoadSizeHint Int + * @param placeHolderEnabled Boolean + * @return LiveData> + */ + private fun initLiveData(dataSource: DataSource, pageSize: Int, initialLoadSizeHint: Int, placeHolderEnabled: Boolean): LiveData> { + val config = PagedList.Config.Builder() + .setPageSize(pageSize) + .setInitialLoadSizeHint(initialLoadSizeHint) + .setEnablePlaceholders(placeHolderEnabled) + .build() + val dataSourceFactory = object : DataSource.Factory() { + override fun create(): DataSource { + return dataSource + } + } + return LivePagedListBuilder(dataSourceFactory, config).build() + } + + /** + * Use this class for create a instance of [Channel] + */ + class ChannelBuilder internal constructor(private val channel: Channel>) { + + fun build(): DataSourceChannel { + return channel as DataSourceChannel + } + } + + + /** + * Use this class for create a instance of [DataSourceChannel] + */ + class Builder { + lateinit var channel : DataSourceChannel + + fun dataSource(dataSourceClass: Class): ChannelBuilder where T : DataSource { + return this.dataSource(dataSourceClass.newInstance()) + } + + fun dataSource(dataSource: DataSource): ChannelBuilder where T : DataSource { + channel = DataSourceChannel() + channel.dataSource = dataSource + return ChannelBuilder(channel) + } + } + +} + +