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"> <component name="Kotlin2JsCompilerArguments">
<option name="sourceMapEmbedSources" /> <option name="sourceMapEmbedSources" />
</component> </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" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -30,8 +30,11 @@ dependencies {
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.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' androidTestImplementation 'com.android.support.test:rules:1.0.2'
implementation 'com.android.support:cardview-v7:28.0.0' 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.Espresso.* import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.withId import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.filters.LargeTest import android.support.test.filters.LargeTest
import android.support.test.rule.ActivityTestRule import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4 import android.support.test.runner.AndroidJUnit4
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.example.h_mal.myapplication.ui.MainActivity
import org.hamcrest.Description import org.hamcrest.Description
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.anything
import org.hamcrest.TypeSafeMatcher import org.hamcrest.TypeSafeMatcher
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith 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 @LargeTest
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@@ -36,24 +30,6 @@ class MainActivityTest {
@Test @Test
fun mainActivityTest() { 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( 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:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".MainActivity"> <activity android:name=".ui.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <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 import com.google.gson.annotations.SerializedName
data class JsonObject( data class Repo(
@SerializedName("name")
var name: String? = null, var name: String? = null,
@SerializedName("description")
var description : String? = null, var description : String? = null,
@SerializedName("language")
var language : String? = null, var language : String? = null,
@SerializedName("created_at")
var date : String? = null, var date : String? = null,
@SerializedName("html_url") var html_url : String? = null)
var repoUrlString : 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.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.net.Uri import android.net.Uri
import android.net.UrlQuerySanitizer
import android.opengl.Visibility
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Filter import com.example.h_mal.myapplication.R
import com.example.h_mal.myapplication.model.JsonObject import com.example.h_mal.myapplication.model.Repo
import kotlinx.android.synthetic.main.repo_list_item.view.* import kotlinx.android.synthetic.main.repo_list_item.view.*
//custom list adapter extends from array adater //custom list adapter extends from array adater
class ListViewAdapter(context: Context, objects: MutableList<JsonObject>) : class ListViewAdapter(context: Context, objects: MutableList<Repo>) :
ArrayAdapter<JsonObject>(context, 0, objects){ ArrayAdapter<Repo>(context, 0, objects){
//populate each view //populate each view
override fun getView(position: Int, convertView: View?, parent: ViewGroup): 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) val dateString = item?.date?.split('T')?.get(0)
view.date?.text = dateString 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 //check if the language object is null
if (item.language != null){ if (item?.language != null){
//language exists in object //language exists in object
//view holdin the language to be visible //view holdin the language to be visible
view.lang_layout.visibility = View.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 //language was null therefore view to be hidden
view.lang_layout.visibility = View.GONE 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 //get the corresponding colour based on the programming language
@@ -83,4 +70,19 @@ class ListViewAdapter(context: Context, objects: MutableList<JsonObject>) :
else -> null 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" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity"> tools:context=".ui.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout <android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh" android:id="@+id/swipe_refresh"
@@ -19,6 +19,41 @@
</android.support.v4.widget.SwipeRefreshLayout> </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> </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" <LinearLayout android:id="@+id/lang_layout" android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_marginTop="6dp" android:gravity="right"> 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"/> tools:text="Java" android:gravity="center" android:id="@+id/lang"/>
<android.support.v7.widget.CardView <android.support.v7.widget.CardView
android:layout_width="16dp" android:layout_width="16dp"
android:layout_height="16dp" android:layout_height="16dp"
android:layout_marginLeft="4dp" android:layout_marginLeft="4dp"
android:layout_gravity="center"
app:cardCornerRadius="8dp" app:cardBackgroundColor="#E03F3F" android:id="@+id/lang_col"/> app:cardCornerRadius="8dp" app:cardBackgroundColor="#E03F3F" android:id="@+id/lang_col"/>
</LinearLayout> </LinearLayout>