안녕하세요. godsangin입니다. 오랜만에 글을 올리게 되었는데요.
최근에 GCP(Google Cloud Platform)을 사용해 보는 관계로 이런 저런 시행착오가 있어서 글을 못썼습니다...ㅠㅠ
오늘은 서버에서 데이터를 불러오기 예제를 진행해보겠습니다.
여러분은 이미지를 포함하는 데이터베이스를 어떻게 설계하시나요 ?? 저는 테이블 안에 url속성을 주고 해당 url에 이미지를 저장한 url을 저장해주는 방식으로 설계하였습니다.(현업에서는 어떻게 하시는지 정말 궁금..)
우선 아래는 제가 만든 Skin이라는 클래스(테이블)입니다.
class Skin(){
var sid = 0
var pid = 0
var title: String? = null
var content: String? = null
var type = 0
var cost = 0
var download = 0
var star = 0
var url: String? = null
}
실제로는 Pacelable을 상속하는 조금 복잡하게 생긴 클래스입니다.(intent로 다른 엑티비티에 넘겨주어야 했기에..)
자 ! 아무튼 객체가 생성되었고 같은 정보로 정의된 테이블이 있다고 가정해봅시다.
그렇다면 Skin이라는 데이터 테이블(select * from Skin)을 전부 조회해서 불러와봅시다.
fun getSkinsTask(){
AsyncTask.execute(object: Runnable {
override fun run() {
var inputStream:InputStream? = null
try{
val myGCPEndpoint = URL("http://YOUR_SERVER_ENDPOINT/YOUR_API_CALL?PARAMS")
val myConnection = myGCPEndpoint.openConnection() as HttpURLConnection
myConnection.setRequestProperty("User-Agent", "my-rest-app-v0.1")
myConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
myConnection.requestMethod = "GET"
myConnection.readTimeout = 10000
myConnection.doInput = true
myConnection.connect()
val response = myConnection.responseCode
Log.d("response==", response.toString())
if(response == 200){
inputStream = myConnection.inputStream
runBlocking{
var job = launch {
skins = readJsonStream(inputStream)
}
job.join()
if(skins != null){
connectionListener.responseEnd()
}
}
}
}catch(e: Exception){
e.printStackTrace()
Log.e("response==", "Error:" + e.message)
}finally {
if(inputStream != null){
inputStream.close()
}
}
}
})
}
서버와 통신하기 위해서는 메인 스레드와는 무관한 작업으로 실행해야 하기 때문에 AsynTask객체를 사용해줍니다.
connection객체에 필요한 속성을 써 주고(request method, timeout, doinput 등) connect메소드를 수행시킵니다.
그리고 connection으로부터 돌려받을 값인 inputStream을 파싱하는 작업이 필요합니다. 이 예제에서는 코루틴을 사용하여 동기적으로 변수를 가져오는 방법을 사용하였습니다.(저도 코루틴에 대해 깊은 이해가 없기 때문에 아래 블로그를 참조하였습니다.)
(수정)Background Task자체도 동기적으로 진행되는 코드이기 때문에 코루틴문은 사용하지 않아도 무방할 것 같습니다..!(그래도 알아두면 좋잖아요..ㅜㅜ)
코루틴 사용법 맛보기 - https://wooooooak.github.io/kotlin/2019/03/17/kotlin_coroutin/
변수를 동기적으로 가져와야 하는 이유는 서버로부터 데이터를 불러온 뒤에 다음 블럭의 코드가 실행되어야 하기 때문입니다.(빈 참조변수를 그대로 view에 바인딩하면 빈 값이 바인드 되겠지요..)
데이터를 다 불러왔다면(데이터가 null이 아닐경우) 커스텀한 connectionListener의 respondEnd함수를 호출합니다. 여기서 respondEnd함수는 불러온 데이터를 통하여 view에 바인드해줍니다.(이미지 + 객체)
오늘 살펴볼 부분은 readJsonStream입니다.
fun readJsonStream(inputStream:InputStream):List<Skin>{
val reader = JsonReader(InputStreamReader(inputStream, "UTF-8"))
try{
return readMessagesArray(reader)
}finally {
reader.close()
}
}
fun readMessagesArray(reader:JsonReader):List<Skin>{
val messages = ArrayList<Skin>()
reader.beginArray()
while(reader.hasNext()){
messages.add(Skin(reader))
}
reader.endArray()
return messages
}
위와 같이 불러온 inputStream을 JsonReader로 생성하고 JsonReader를 통하여 JSON으로 표현된 SkinList데이터를 List로 파싱하여 반환하는 것입니다.
이를 위하여 앞서 만들었던 Skin클래스를 아래와 같이 바꿔줍니다.
import android.util.JsonReader
import android.util.Log
class Skin(){
var sid = 0
var pid = 0
var title: String? = null
var content: String? = null
var type = 0
var cost = 0
var download = 0
var star = 0
var url: String? = null
constructor(reader: JsonReader):this(){
reader.beginObject()
while(reader.hasNext()){
var name = reader.nextName()
if(name.equals("sid")){
sid = reader.nextInt()
}else if(name.equals("pid")){
pid = reader.nextInt()
}else if(name.equals("title")){
title = reader.nextString()
}else if(name.equals("content")){
content = reader.nextString()
}else if(name.equals("type")){
type = reader.nextInt()
}else if(name.equals("cost")){
cost = reader.nextInt()
}else if(name.equals("download")){
download = reader.nextInt()
}else if(name.equals("star")){
star = reader.nextInt()
}else if(name.equals("url")){
url = reader.nextString()
}else{
reader.skipValue()
}
}
reader.endObject()
}
}
JSON객체를 하나씩 읽어서 각각의 속성에 저장해줍니다. 변수 개수가 안맞거나 필드명이 다르면 계속해서 오류가 납니다.ㅠㅠㅠ
이렇게 해서 데이터를 전부 불러온 다음에 listener를 통하여 다른 Thread를 생성하여 View작업을 진행합니다.(background작업에서는 view를 바꾸는 작업을 할 수 없습니다.)
저는 이렇게 불러온 Skin이라는 데이터를 통하여 받은 이미지 url을 통하여 다시 한번 이미지를 불러오는 과정을 거쳐 이미지를 표현할 수 있었습니다.
아래는 전체 코드입니다. 아마 대부분이 본인 환경에 맞춰 바꾸실 것이라고 생각하기 때문에 view작업에 관한 코드는 생략하도록 하겠습니다.
궁금하신 점이 있다면 편하게 댓글 달아주시면 감사하겠습니다~~!
import android.os.*
import androidx.appcompat.app.AppCompatActivity
import android.util.JsonReader
import android.util.Log
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.myhome.rpgkeyboard.R
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.InputStream
import java.io.InputStreamReader
import java.lang.Exception
import java.net.HttpURLConnection
import java.net.URL
class SomethingActivity : AppCompatActivity(){
lateinit var mRecyclerView:RecyclerView
lateinit var settingHeader:ConstraintLayout
lateinit var recyclerAdapter:SkinAdapter
var skins :List<Skin>? = null
val connectionListener = object: ConnectionListener{
override fun responseEnd() {//데이터를 전부 받아왔을 경우 실행됨
object: Thread(){
override fun run() {
super.run()
val msg = adapterFrontTask.obtainMessage()
adapterFrontTask.sendMessage(msg)
}
}.run()
adapterFrontTask
}
}
val adapterFrontTask:Handler = object:Handler(){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
recyclerAdapter =
SkinAdapter(applicationContext, skins!!)
// val gl = GridLayoutManager(applicationContext, 2)
val lm = LinearLayoutManager(applicationContext)
mRecyclerView.layoutManager = lm
mRecyclerView.setHasFixedSize(true)
mRecyclerView.adapter = recyclerAdapter
recyclerAdapter.notifyDataSetChanged()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_setting_theme)
mRecyclerView = findViewById(R.id.theme_recyclerview)
settingHeader = findViewById(R.id.setting_header)
getSkinsTask()
}
fun getSkinsTask(){
AsyncTask.execute(object: Runnable {
override fun run() {
var inputStream:InputStream? = null
try{
val myGCPEndpoint = URL("http://YOUR_SERVER_ENDPOINT/YOUR_API_CALL?PARAMS")
val myConnection = myGCPEndpoint.openConnection() as HttpURLConnection
myConnection.setRequestProperty("User-Agent", "my-rest-app-v0.1")
myConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
myConnection.requestMethod = "GET"
myConnection.readTimeout = 10000
myConnection.doInput = true
myConnection.connect()
val response = myConnection.responseCode
Log.d("response==", response.toString())
if(response == 200){
inputStream = myConnection.inputStream
runBlocking{
var job = launch {
skins = readJsonStream(inputStream)
}
job.join()
if(skins != null){
connectionListener.responseEnd()
}
}
}
}catch(e: Exception){
e.printStackTrace()
Log.e("response==", "Error:" + e.message)
}finally {
if(inputStream != null){
inputStream.close()
}
}
}
})
}
fun readJsonStream(inputStream:InputStream):List<Skin>{
val reader = JsonReader(InputStreamReader(inputStream, "UTF-8"))
try{
return readMessagesArray(reader)
}finally {
reader.close()
}
}
fun readMessagesArray(reader:JsonReader):List<Skin>{
val messages = ArrayList<Skin>()
reader.beginArray()
while(reader.hasNext()){
messages.add(Skin(reader))
}
reader.endArray()
return messages
}
}
아래는 제가 참고한 자바로 작성하신 분의 블로그입니다 :)
참고 사이트 - https://readystory.tistory.com/18
'IT 프로그래밍-Android' 카테고리의 다른 글
[Android] RecyclerView 무한스크롤(endless scroll) 만들기 (0) | 2020.01.16 |
---|---|
[Android] Glide 라이브러리 gif파일 로드 (0) | 2020.01.10 |
[Android] 키보드 앱 만들기 후기 (12) | 2019.11.20 |
[Android]커스텀 키보드 만들기(4/4) - 천지인 키보드 만들기 (0) | 2019.11.12 |
[Android] 커스텀 키보드 만들기(3/4) 외전 - HangulMaker (2) | 2019.11.08 |