New commit with added features:

- implement retrofit and rxjava
This commit is contained in:
2019-12-05 23:47:46 +11:00
parent f917dabe3f
commit 15ffb44d53
11 changed files with 236 additions and 207 deletions

2
.idea/misc.xml generated
View File

@@ -8,7 +8,7 @@
<component name="Kotlin2JsCompilerArguments">
<option name="sourceMapEmbedSources" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@@ -30,8 +30,11 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.squareup.okhttp3:okhttp:4.2.1'
implementation 'com.google.code.gson:gson:2.8.5'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
}

View File

@@ -2,28 +2,22 @@ package com.example.h_mal.myapplication
import android.support.test.espresso.Espresso
import android.support.test.espresso.Espresso.*
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.matcher.ViewMatchers.withId
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.filters.LargeTest
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import android.view.View
import android.view.ViewGroup
import com.example.h_mal.myapplication.ui.MainActivity
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.anything
import org.hamcrest.TypeSafeMatcher
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import android.support.test.espresso.UiController
import android.support.test.orchestrator.junit.BundleJUnitUtils.getDescription
import android.support.test.espresso.ViewAction
import android.support.test.espresso.action.ViewActions.swipeDown
import android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast
@LargeTest
@RunWith(AndroidJUnit4::class)
@@ -36,24 +30,6 @@ class MainActivityTest {
@Test
fun mainActivityTest() {
onIdle()
val relativeLayout = onData(anything())
.inAdapterView(
allOf(
withId(R.id.list_view),
childAtPosition(
withId(R.id.swipe_refresh),
0
)
)
)
.atPosition(0)
relativeLayout.perform(click())
onView(withId(R.id.swipe_refresh))
.perform(withCustomConstraints(swipeDown(), isDisplayingAtLeast(85)))
}
private fun childAtPosition(
@@ -73,20 +49,4 @@ class MainActivityTest {
}
}
}
fun withCustomConstraints(action: ViewAction, constraints: Matcher<View>): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> {
return constraints
}
override fun getDescription(): String {
return action.description
}
override fun perform(uiController: UiController, view: View) {
action.perform(uiController, view)
}
}
}
}

View File

@@ -12,7 +12,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<activity android:name=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

View File

@@ -0,0 +1,11 @@
package com.example.h_mal.myapplication.Api
import com.example.h_mal.myapplication.model.Repo
import io.reactivex.Observable
import retrofit2.http.GET
interface GetData{
@GET("repos")
fun getData() : Observable<List<Repo>>
}

View File

@@ -1,121 +0,0 @@
package com.example.h_mal.myapplication
import android.content.Context
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v4.widget.SwipeRefreshLayout
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.SearchView
import com.example.h_mal.myapplication.model.JsonObject
import com.google.gson.Gson
import kotlinx.android.synthetic.main.activity_main.*
import okhttp3.*
import java.io.IOException
class MainActivity : AppCompatActivity() {
lateinit var searchView: SearchView
val urlString = "https://api.github.com/orgs/square/repos"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//begin populating list
executeTask()
//set a listener for the swipe to refresh
swipe_refresh.setOnRefreshListener(swipeRefreshListener)
}
//implement search interface in the menu
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
//inflate custom menu as our menu
menuInflater.inflate(R.menu.menu, menu)
//extract searchview
val searchItem = menu?.findItem(R.id.search)
//set searchview globally
searchView = searchItem?.actionView as SearchView
return true
}
val swipeRefreshListener = SwipeRefreshLayout.OnRefreshListener{
//populate list when pulling to refresh
executeTask()
}
fun executeTask(){
//clear list before populating
list_view.adapter = null
//create url from url string urlstring
val url = Request.Builder().url(urlString).build()
//create a okhttpclient for a get request from url
val client = OkHttpClient()
//call the url and retrieve its callback
client.newCall(url).enqueue(object: Callback {
//failure of retrieval callback
override fun onFailure(call: Call, e: IOException) {
//print error to log
System.err.println(e)
//if swipe refresh is refreshing then stop
swipe_refresh.isRefreshing = false
//list is empty
}
//successful retrieval callback
override fun onResponse(call: Call, response: Response) {
//get the JSON from the body
val urlResponse = response.body?.string()
//print the response to the logs
println("response = $urlResponse")
//create gson object
val gson = Gson()
//create a mutable list of objects extracted from the json text
val objectList = gson.fromJson(urlResponse, Array<JsonObject>::class.java).asList().toMutableList()
if (objectList.size >0){
//update the ui
runOnUiThread{
//custom list view adapter created
val adapterLV = ListViewAdapter(baseContext, objectList)
//apply adapter to listview
list_view.adapter = adapterLV
//if swipe refresh is refreshing then stop
swipe_refresh.isRefreshing = false
//search view has its query change listener applied
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
//as the test is changed the list is filtered
override fun onQueryTextChange(newText: String?): Boolean {
//filter list function
adapterLV.filter.filter(newText)
return true
}
})
}
}else{
//list is empty
}
}
})
}
}

View File

@@ -2,15 +2,9 @@ package com.example.h_mal.myapplication.model
import com.google.gson.annotations.SerializedName
data class JsonObject(
@SerializedName("name")
data class Repo(
var name: String? = null,
@SerializedName("description")
var description : String? = null,
@SerializedName("language")
var language : String? = null,
@SerializedName("created_at")
var date : String? = null,
@SerializedName("html_url")
var repoUrlString : String? = null
)
var html_url : String? = null)

View File

@@ -1,21 +1,20 @@
package com.example.h_mal.myapplication
package com.example.h_mal.myapplication.ui
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.net.Uri
import android.net.UrlQuerySanitizer
import android.opengl.Visibility
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Filter
import com.example.h_mal.myapplication.model.JsonObject
import com.example.h_mal.myapplication.R
import com.example.h_mal.myapplication.model.Repo
import kotlinx.android.synthetic.main.repo_list_item.view.*
//custom list adapter extends from array adater
class ListViewAdapter(context: Context, objects: MutableList<JsonObject>) :
ArrayAdapter<JsonObject>(context, 0, objects){
class ListViewAdapter(context: Context, objects: MutableList<Repo>) :
ArrayAdapter<Repo>(context, 0, objects){
//populate each view
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
@@ -36,8 +35,15 @@ class ListViewAdapter(context: Context, objects: MutableList<JsonObject>) :
val dateString = item?.date?.split('T')?.get(0)
view.date?.text = dateString
//control the language section of the view
populateLanguage(item, view)
return view
}
fun populateLanguage(item : Repo?, view : View){
//check if the language object is null
if (item.language != null){
if (item?.language != null){
//language exists in object
//view holdin the language to be visible
view.lang_layout.visibility = View.VISIBLE
@@ -49,25 +55,6 @@ class ListViewAdapter(context: Context, objects: MutableList<JsonObject>) :
//language was null therefore view to be hidden
view.lang_layout.visibility = View.GONE
}
//apply on click listener to item
view.setOnClickListener{
//click item opens then url
openLink(item.repoUrlString)
}
return view
}
//function for opening the link
fun openLink(urlString : String?){
//open link to repo if the url is not nu;;
if (urlString != null){
val openURL = Intent(Intent.ACTION_VIEW)
openURL.data = Uri.parse(urlString)
context.startActivity(openURL)
}
}
//get the corresponding colour based on the programming language
@@ -83,4 +70,19 @@ class ListViewAdapter(context: Context, objects: MutableList<JsonObject>) :
else -> null
}
}
//function for opening the link
fun openLink(position: Int){
val urlString = getItem(position)?.html_url
//open link to repo if the url is not null
if (urlString != null){
val openURL = Intent(Intent.ACTION_VIEW)
openURL.addFlags(FLAG_ACTIVITY_NEW_TASK)
openURL.data = Uri.parse(urlString)
context.startActivity(openURL)
}
}
}

View File

@@ -0,0 +1,144 @@
package com.example.h_mal.myapplication.ui
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v4.widget.SwipeRefreshLayout
import android.view.Menu
import android.view.View
import android.widget.SearchView
import com.example.h_mal.myapplication.Api.GetData
import com.example.h_mal.myapplication.R
import com.example.h_mal.myapplication.model.Repo
import kotlinx.android.synthetic.main.activity_main.*
import io.reactivex.disposables.CompositeDisposable
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.Retrofit
import io.reactivex.schedulers.Schedulers
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.observers.DisposableObserver
class MainActivity : AppCompatActivity() {
lateinit var searchView: SearchView
lateinit var myCompositeDisposable: CompositeDisposable
val urlString = "https://api.github.com/orgs/square/"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
spinner.visibility = View.VISIBLE
myCompositeDisposable = CompositeDisposable()
//begin populating list
loadData()
//set a listener for the swipe to refresh
swipe_refresh.setOnRefreshListener(swipeRefreshListener)
}
//implement search interface in the menu
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
//inflate custom menu as our menu
menuInflater.inflate(R.menu.menu, menu)
//extract searchview
val searchItem = menu?.findItem(R.id.search)
//set searchview globally
searchView = searchItem?.actionView as SearchView
return true
}
override fun onDestroy() {
super.onDestroy()
myCompositeDisposable?.clear()
}
val swipeRefreshListener = SwipeRefreshLayout.OnRefreshListener{
//populate list when pulling to refresh
// callData()
loadData()
}
fun loadData(){
//clear list before populating
list_view.adapter = null
val requestInterface = Retrofit.Builder()
.baseUrl(urlString)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(GetData::class.java)
myCompositeDisposable.add(requestInterface.getData()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribeWith(object : DisposableObserver<List<Repo>>() {
override fun onNext(t: List<Repo>) {
handleResponse(t)
}
override fun onComplete() {
}
override fun onError(e: Throwable) {
handleError()
}
}))
}
private fun handleResponse(objectList: List<Repo>) {
spinner.visibility = View.GONE
//if swipe refresh is refreshing then stop
swipe_refresh.isRefreshing = false
if (objectList.isNotEmpty()){
//list is not empty
empty_view.visibility = View.GONE
//custom list view adapter created
val adapterLV = ListViewAdapter(baseContext, objectList.toMutableList())
//apply adapter to listview
list_view.adapter = adapterLV
list_view.setOnItemClickListener { parent, view, position, id ->
adapterLV.openLink(position)
}
//search view has its query change listener applied
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
//as the test is changed the list is filtered
override fun onQueryTextChange(newText: String?): Boolean {
//filter list function
adapterLV.filter.filter(newText)
return true
}
})
}
}
fun handleError(){
//if swipe refresh is refreshing then stop
swipe_refresh.isRefreshing = false
//list is empty
empty_view.visibility = View.VISIBLE
//progress bar hidden
spinner.visibility = View.GONE
}
}

View File

@@ -5,7 +5,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
tools:context=".ui.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
@@ -19,6 +19,41 @@
</android.support.v4.widget.SwipeRefreshLayout>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<RelativeLayout
android:id="@+id/empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/empty_title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:fontFamily="sans-serif-medium"
android:paddingTop="16dp"
android:text="list empty"
android:textAppearance="?android:textAppearanceMedium"/>
<TextView
android:id="@+id/empty_subtitle_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/empty_title_text"
android:layout_centerHorizontal="true"
android:fontFamily="sans-serif"
android:paddingTop="8dp"
android:text="please check connection"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="#A2AAB0"/>
</RelativeLayout>
<ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/spinner" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>

View File

@@ -66,12 +66,13 @@ more text more of a description." />
<LinearLayout android:id="@+id/lang_layout" android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_marginTop="6dp" android:gravity="right">
<TextView android:layout_width="wrap_content" android:layout_height="match_parent"
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
tools:text="Java" android:gravity="center" android:id="@+id/lang"/>
<android.support.v7.widget.CardView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginLeft="4dp"
android:layout_gravity="center"
app:cardCornerRadius="8dp" app:cardBackgroundColor="#E03F3F" android:id="@+id/lang_col"/>
</LinearLayout>