mirror of
https://github.com/hmalik144/Candy_Space_tech_test.git
synced 2025-12-10 03:05:27 +00:00
View binding added, basic repository unit tests added
This commit is contained in:
@@ -24,6 +24,10 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -37,6 +41,8 @@ dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
//mockito
|
||||
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||
|
||||
//Retrofit and GSON
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
|
||||
@@ -56,11 +62,7 @@ dependencies {
|
||||
implementation "org.kodein.di:kodein-di-generic-jvm:6.2.1"
|
||||
implementation "org.kodein.di:kodein-di-framework-android-x:6.2.1"
|
||||
|
||||
//Android Room
|
||||
implementation "androidx.room:room-runtime:2.2.5"
|
||||
implementation "androidx.room:room-ktx:2.2.5"
|
||||
kapt "androidx.room:room-compiler:2.2.5"
|
||||
|
||||
//recycler view item data binding
|
||||
implementation 'com.xwray:groupie:2.3.0'
|
||||
implementation 'com.xwray:groupie-kotlin-android-extensions:2.3.0'
|
||||
implementation 'com.xwray:groupie-databinding:2.3.0'
|
||||
|
||||
@@ -3,5 +3,5 @@ package com.example.h_mal.candyspace.ui.main
|
||||
interface CompletionListener {
|
||||
fun onStarted()
|
||||
fun onSuccess()
|
||||
fun onFailure()
|
||||
fun onFailure(message: String)
|
||||
}
|
||||
@@ -1,14 +1,28 @@
|
||||
package com.example.h_mal.candyspace.ui.main
|
||||
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.example.h_mal.candyspace.R
|
||||
import com.example.h_mal.candyspace.databinding.MainFragmentBinding
|
||||
import org.kodein.di.Kodein
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.kodein
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
class MainFragment : Fragment() {
|
||||
/*
|
||||
* UI for the first screen holding the list, search box
|
||||
*/
|
||||
class MainFragment : Fragment(), KodeinAware{
|
||||
|
||||
//retrieve the viewmodel factory from the kodein dependency injection
|
||||
override val kodein by lazy { (context as KodeinAware).kodein }
|
||||
private val factory : MainViewModelFactory by instance()
|
||||
|
||||
companion object {
|
||||
fun newInstance() = MainFragment()
|
||||
@@ -18,13 +32,20 @@ class MainFragment : Fragment() {
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View {
|
||||
return inflater.inflate(R.layout.main_fragment, container, false)
|
||||
//retrieve viewmodel from viewmodel factory
|
||||
viewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java)
|
||||
//Bind layout to Viewmodel
|
||||
val binding: MainFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false)
|
||||
binding.viewmodel = viewModel
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
|
||||
// TODO: Use the ViewModel
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ package com.example.h_mal.candyspace.ui.main
|
||||
import android.view.View
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.example.h_mal.candyspace.data.repositories.Repository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
|
||||
class MainViewModel(
|
||||
private val repository: Repository
|
||||
@@ -10,7 +14,26 @@ class MainViewModel(
|
||||
|
||||
var searchString: String? = null
|
||||
|
||||
fun submit(view: View){
|
||||
val completionListener: CompletionListener? = null
|
||||
|
||||
fun submit(view: View){
|
||||
completionListener?.onStarted()
|
||||
if (searchString.isNullOrEmpty()){
|
||||
completionListener?.onFailure("Search box is empty")
|
||||
return
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val user = repository.getUsers(searchString!!)
|
||||
//todo: update live data
|
||||
|
||||
//successfully finished
|
||||
completionListener?.onSuccess()
|
||||
}catch (e: IOException){
|
||||
completionListener?.onFailure(e.message!!)
|
||||
}
|
||||
completionListener?.onFailure("Failed to retrieve Users")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.example.h_mal.candyspace.ui.main
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.example.h_mal.candyspace.data.repositories.Repository
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class MainViewModelFactory(
|
||||
private val repository: Repository
|
||||
) : ViewModelProvider.NewInstanceFactory(){
|
||||
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return MainViewModel(repository) as T
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.main.MainFragment">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<LinearLayout
|
||||
<data>
|
||||
<variable
|
||||
name="viewmodel"
|
||||
type="com.example.h_mal.candyspace.ui.main.MainViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_margin="4dp"
|
||||
android:orientation="horizontal"
|
||||
android:id="@+id/container">
|
||||
<EditText
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/submit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.main.MainFragment">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toEndOf="@id/search_bar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:text="Submit"/>
|
||||
</LinearLayout>
|
||||
android:layout_margin="4dp"
|
||||
android:orientation="horizontal"
|
||||
android:id="@+id/container">
|
||||
<EditText
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<view class="androidx.appcompat.app.AlertController$RecycleListView"
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/container"
|
||||
tools:listitem="@android:layout/simple_list_item_2">
|
||||
<Button
|
||||
android:id="@+id/submit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toEndOf="@id/search_bar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:text="Submit"/>
|
||||
</LinearLayout>
|
||||
|
||||
</view>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/container"
|
||||
tools:listitem="@android:layout/simple_list_item_2">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
||||
@@ -1,12 +1,65 @@
|
||||
package com.example.h_mal.candyspace.data.repositories
|
||||
|
||||
import android.util.Log
|
||||
import com.example.h_mal.candyspace.data.api.ApiClass
|
||||
import com.example.h_mal.candyspace.data.api.User
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.ResponseBody
|
||||
import org.junit.Before
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import retrofit2.Response
|
||||
|
||||
class RepositoryTest {
|
||||
|
||||
lateinit var repository: Repository
|
||||
|
||||
@Mock
|
||||
lateinit var api: ApiClass
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
repository = Repository(api)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchUserFromApi_positiveResponse() = runBlocking {
|
||||
val mockUser = mock(User::class.java)
|
||||
val mockResponse = Response.success(mockUser)
|
||||
|
||||
Mockito.`when`(api.getUsersFromApi("12345")).thenReturn(
|
||||
mockResponse
|
||||
)
|
||||
|
||||
val getUser = repository.getUsers("12345")
|
||||
|
||||
assertNotNull(getUser)
|
||||
assertEquals(mockUser, getUser)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchUserFromApi_negativeResponse() = runBlocking {
|
||||
val mockResponse = Response.error<User>(403,
|
||||
ResponseBody.create(
|
||||
MediaType.parse("application/json"),
|
||||
"{\"key\":[\"somestuff\"]}"
|
||||
))
|
||||
|
||||
|
||||
Mockito.`when`(api.getUsersFromApi("12345")).thenReturn(
|
||||
mockResponse
|
||||
)
|
||||
|
||||
val getUser = repository.getUsers("12345")
|
||||
assertNotNull(getUser)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user