diff --git a/app/build.gradle b/app/build.gradle index e5ae03a..5ee2c1a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' + id 'androidx.navigation.safeargs' } android { @@ -53,10 +54,15 @@ android { } dependencies { - def room_version = "2.2.6" + implementation 'androidx.legacy:legacy-support-v4:1.0.0' +// def room_version = "2.2.6" + def room_version = "2.3.0-alpha01" + def lifecycle_version = "2.3.0" def activity_version = "1.2.0" def work_version = "2.5.0" + def nav_version = "2.3.3" + def koin_version = '2.2.2' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' @@ -81,7 +87,22 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' // Kotlin + coroutines implementation "androidx.work:work-runtime-ktx:$work_version" + //Navigation kotlin + implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" + implementation "androidx.navigation:navigation-ui-ktx:$nav_version" + // Koin + implementation "org.koin:koin-core:$koin_version" + // Koin for Android + implementation "org.koin:koin-android:$koin_version" + // Koin AndroidX Scope features + implementation "org.koin:koin-androidx-scope:$koin_version" + // Koin AndroidX ViewModel features + implementation "org.koin:koin-androidx-viewmodel:$koin_version" + // Koin AndroidX Experimental features + implementation "org.koin:koin-androidx-ext:$koin_version" + // Testing + testImplementation "org.koin:koin-test:$koin_version" testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'com.google.truth:truth:1.1.2' diff --git a/app/src/androidTest/java/com/macoev/roomsample/MainActivityTest.kt b/app/src/androidTest/java/com/macoev/roomsample/MainActivityTest.kt index eb2ca3c..753f5e8 100644 --- a/app/src/androidTest/java/com/macoev/roomsample/MainActivityTest.kt +++ b/app/src/androidTest/java/com/macoev/roomsample/MainActivityTest.kt @@ -16,6 +16,7 @@ import androidx.test.espresso.matcher.ViewMatchers.hasChildCount import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.macoev.roomsample.activity.MainActivity import com.macoev.roomsample.adapter.UserViewHolder import com.macoev.roomsample.data.repository.Repository import org.hamcrest.Matcher diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 18cc975..a455a04 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,8 +8,10 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:name=".application.App" android:theme="@style/Theme.RoomSample"> - + + diff --git a/app/src/main/java/com/macoev/roomsample/RecyclerViewBindings.kt b/app/src/main/java/com/macoev/roomsample/RecyclerViewBindings.kt deleted file mode 100644 index b71c5af..0000000 --- a/app/src/main/java/com/macoev/roomsample/RecyclerViewBindings.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.macoev.roomsample - -import androidx.databinding.BindingAdapter -import androidx.recyclerview.widget.RecyclerView - -object RecyclerViewBindings { - @JvmStatic - @BindingAdapter("adapter") - fun adapter(recyclerView: RecyclerView, adapter: RecyclerView.Adapter) { - recyclerView.adapter = adapter - } -} \ No newline at end of file diff --git a/app/src/main/java/com/macoev/roomsample/MainActivity.kt b/app/src/main/java/com/macoev/roomsample/activity/MainActivity.kt similarity index 63% rename from app/src/main/java/com/macoev/roomsample/MainActivity.kt rename to app/src/main/java/com/macoev/roomsample/activity/MainActivity.kt index cebdaf9..c7f0f47 100644 --- a/app/src/main/java/com/macoev/roomsample/MainActivity.kt +++ b/app/src/main/java/com/macoev/roomsample/activity/MainActivity.kt @@ -1,27 +1,27 @@ -package com.macoev.roomsample +package com.macoev.roomsample.activity import android.os.Build import android.os.Bundle import android.widget.Toast -import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil +import com.macoev.roomsample.R import com.macoev.roomsample.databinding.ActivityMainBinding import com.macoev.roomsample.viewmodel.UserViewModel import com.macoev.roomsample.work.DownloadWork import com.macoev.roomsample.work.RequestManager +import org.koin.androidx.viewmodel.ext.android.viewModel import java.time.Duration class MainActivity : AppCompatActivity() { - private val model: UserViewModel by viewModels() + private val model: UserViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.viewModel = model - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { syncServer() } @@ -32,10 +32,15 @@ class MainActivity : AppCompatActivity() { RequestManager(applicationContext).run { periodic(interval = Duration.ofMinutes(15)) .onError { - Toast.makeText(this@MainActivity, "onError called", Toast.LENGTH_SHORT).show() + Toast.makeText(applicationContext, "onError called", Toast.LENGTH_SHORT).show() }.onSuccess { - Toast.makeText(this@MainActivity, "onSuccess called", Toast.LENGTH_SHORT).show() + Toast.makeText(applicationContext, "onSuccess called", Toast.LENGTH_SHORT) + .show() } } } + + override fun onBackPressed() { + super.onBackPressed() + } } \ No newline at end of file diff --git a/app/src/main/java/com/macoev/roomsample/adapter/UserAdapter.kt b/app/src/main/java/com/macoev/roomsample/adapter/UserAdapter.kt index 49df7c4..3d98594 100644 --- a/app/src/main/java/com/macoev/roomsample/adapter/UserAdapter.kt +++ b/app/src/main/java/com/macoev/roomsample/adapter/UserAdapter.kt @@ -11,7 +11,7 @@ import com.macoev.roomsample.R import com.macoev.roomsample.data.User import com.macoev.roomsample.data.repository.Repository -class UserAdapter(val repository: Repository) : RecyclerView.Adapter() { +class UserAdapter(val repository: Repository, val tap: (User) -> Unit) : RecyclerView.Adapter() { private var data: ArrayList = arrayListOf() @@ -31,13 +31,13 @@ class UserAdapter(val repository: Repository) : RecyclerView.Adapter> + fun findAll(): LiveData> @Query("SELECT * FROM user WHERE id IN (:userIds)") - fun loadAllByIds(userIds: IntArray): LiveData> + fun findByIds(userIds: IntArray): LiveData> @Query("SELECT * FROM user WHERE full_name LIKE :fullName LIMIT 1") fun findFirstByName(fullName: String): LiveData @Query("SELECT * FROM user WHERE full_name LIKE :fullName") - fun findByName(fullName: String): LiveData> + fun findAllByName(fullName: String): LiveData> - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertAll(vararg users: User) + @Insert(onConflict = OnConflictStrategy.REPLACE) //AUTO GENERATED IDS WILL CHANGE WHEN ROWS GET REPLACED + fun insertOrUpdate(vararg users: User) + + //@Update + //fun update(user: User) @Delete fun delete(user: User) diff --git a/app/src/main/java/com/macoev/roomsample/data/repository/UserRepository.kt b/app/src/main/java/com/macoev/roomsample/data/repository/UserRepository.kt index 7012db3..185bf02 100644 --- a/app/src/main/java/com/macoev/roomsample/data/repository/UserRepository.kt +++ b/app/src/main/java/com/macoev/roomsample/data/repository/UserRepository.kt @@ -7,15 +7,15 @@ import kotlinx.coroutines.* class UserRepository(private val dao: UserDao) : Repository { private val scope = CoroutineScope(Job() + Dispatchers.IO) - override fun getAllUsers() = dao.getAll() + override fun getAllUsers() = dao.findAll() - override fun insert(vararg users: User) = scope.launch { dao.insertAll(*users) } + override fun insert(vararg users: User) = scope.launch { dao.insertOrUpdate(*users) } override fun delete(user: User) = scope.launch { dao.delete(user) } - override fun findBy(name: String) = dao.findByName(name) + override fun findBy(name: String) = dao.findAllByName(name) - override fun findByIds(vararg ids: Int) = dao.loadAllByIds(ids) + override fun findByIds(vararg ids: Int) = dao.findByIds(ids) override fun deleteAll() = runBlocking { dao.deleteAll() } } \ No newline at end of file diff --git a/app/src/main/java/com/macoev/roomsample/fragment/MainFragment.kt b/app/src/main/java/com/macoev/roomsample/fragment/MainFragment.kt new file mode 100644 index 0000000..85849d2 --- /dev/null +++ b/app/src/main/java/com/macoev/roomsample/fragment/MainFragment.kt @@ -0,0 +1,45 @@ +package com.macoev.roomsample.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.distinctUntilChanged +import androidx.navigation.fragment.findNavController +import com.macoev.roomsample.R +import com.macoev.roomsample.databinding.MainFragmentBinding +import com.macoev.roomsample.viewmodel.UserViewModel +import org.koin.androidx.viewmodel.ext.android.sharedViewModel +import org.koin.core.parameter.parametersOf + +class MainFragment : Fragment() { + + private var binding: MainFragmentBinding?=null + private val model: UserViewModel by sharedViewModel() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = MainFragmentBinding.inflate(inflater, container, false) + return binding!!.root + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + binding!!.viewModel = model + model.selectedUser.distinctUntilChanged().observe(requireActivity(), { + it?.let { + findNavController().navigate(MainFragmentDirections.actionMainFragmentToUserDetailFragment()) + } + }) + } + + companion object { + fun newInstance() = MainFragment() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/macoev/roomsample/fragment/UserDetailFragment.kt b/app/src/main/java/com/macoev/roomsample/fragment/UserDetailFragment.kt new file mode 100644 index 0000000..6dbabfb --- /dev/null +++ b/app/src/main/java/com/macoev/roomsample/fragment/UserDetailFragment.kt @@ -0,0 +1,36 @@ +package com.macoev.roomsample.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.macoev.roomsample.databinding.UserDetailFragmentBinding +import com.macoev.roomsample.viewmodel.UserViewModel +import org.koin.androidx.viewmodel.ext.android.sharedViewModel + +class UserDetailFragment : Fragment() { + + companion object { + fun newInstance() = UserDetailFragment() + } + + private lateinit var binding: UserDetailFragmentBinding + private val viewModel: UserViewModel by sharedViewModel() +// private val args: UserDetailFragmentArguments by navArgs() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = UserDetailFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + binding.viewModel = viewModel + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/macoev/roomsample/utils/RecyclerViewBindings.kt b/app/src/main/java/com/macoev/roomsample/utils/RecyclerViewBindings.kt new file mode 100644 index 0000000..36acc46 --- /dev/null +++ b/app/src/main/java/com/macoev/roomsample/utils/RecyclerViewBindings.kt @@ -0,0 +1,32 @@ +package com.macoev.roomsample.utils + +import android.view.MotionEvent +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView + +object RecyclerViewBindings { + @JvmStatic + @BindingAdapter("adapter") + fun adapter( + recyclerView: RecyclerView, + adapter: RecyclerView.Adapter + ) { + recyclerView.adapter = adapter + } + + @JvmStatic + @BindingAdapter("itemTap") + fun itemTap(recyclerView: RecyclerView, click: (Int) -> Boolean) { + recyclerView.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() { + override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { + val child = recyclerView.findChildViewUnder(e.x, e.y) + child?.let { + val pos = rv.getChildAdapterPosition(it) + return click(pos) + } + return false + } + + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/macoev/roomsample/viewmodel/UserViewModel.kt b/app/src/main/java/com/macoev/roomsample/viewmodel/UserViewModel.kt index 8973577..0af1154 100644 --- a/app/src/main/java/com/macoev/roomsample/viewmodel/UserViewModel.kt +++ b/app/src/main/java/com/macoev/roomsample/viewmodel/UserViewModel.kt @@ -2,16 +2,22 @@ package com.macoev.roomsample.viewmodel import android.app.Application import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData import com.macoev.roomsample.RepositoryLocator import com.macoev.roomsample.adapter.UserAdapter import com.macoev.roomsample.data.User import com.macoev.roomsample.data.repository.Repository -class UserViewModel(application: Application) : AndroidViewModel(application) { +open class UserViewModel(application: Application) : AndroidViewModel(application) { private var repository: Repository = RepositoryLocator.get(application) - var adapter: UserAdapter = UserAdapter(repository) + val tap: (User) -> Unit = { user -> selectedUser.value = user } + + var adapter: UserAdapter = UserAdapter(repository, tap) + + var selectedUser = MutableLiveData() + private set fun getAllUsers() = repository.getAllUsers() diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c3b8f96..5d4d84b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,19 +4,29 @@ xmlns:tools="http://schemas.android.com/tools"> - - + tools:context=".activity.MainActivity"> + + - - \ No newline at end of file diff --git a/app/src/main/res/layout/main_fragment.xml b/app/src/main/res/layout/main_fragment.xml new file mode 100644 index 0000000..2898096 --- /dev/null +++ b/app/src/main/res/layout/main_fragment.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/user_detail_fragment.xml b/app/src/main/res/layout/user_detail_fragment.xml new file mode 100644 index 0000000..1289d49 --- /dev/null +++ b/app/src/main/res/layout/user_detail_fragment.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 0000000..1de34fb --- /dev/null +++ b/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fd905b5..7f6c20d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ RoomSample + + Hello blank fragment \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9d55db3..ef92e32 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,10 @@ buildscript { jcenter() } dependencies { + def nav_version = "2.3.3" classpath "com.android.tools.build:gradle:4.1.2" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle.properties b/gradle.properties index d3ea300..205bcd1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,4 +19,4 @@ android.useAndroidX=true android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -android.databinding.incremental=true +android.databinding.incremental=true \ No newline at end of file