이동식 저장소

MiTweet 개발일지 7 본문

프로젝트/MiTweet

MiTweet 개발일지 7

해스끼 2020. 9. 4. 13:44

이번 일지에서는 로컬 트윗 데이터베이스를 만들고, 타임라인을 불러와 보여주면서 데이터베이스에 저장하는 과정에 대해 서술해 본다. 사실 이번 글도 생략된 부분이 많다. 중간 과정을 보는 재미도 있긴 한데 코딩할 때는 딴 생각이 잘 안 나서..

데이터베이스 구현

올해 초에 단어장 앱을 만들 때는 ``SQLite``를 사용했었는데, 요즘은 ``NoSQL(Not only SQL)``이 각광받고 있다고 하여  여러 구현체 중 오픈 소스 ``MongoDB Realm``을 사용해 보기로 했다.

// at build.gradle(project level)

    dependencies {
        // ...
        classpath 'io.realm:realm-gradle-plugin:7.0.0'
    }
// at build.gradle(app level)

apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'

``Realm``에 넣을 데이터 스키마를 정의한다. 이때 ``RealmObject``를 상속해야 하며, 클래스와 필드 모두 ``open``으로 정의해야 한다. 지금은 트윗 객체를 넣을 것이므로 트윗의 ID와 로그인한 계정의 ID를 저장한다. 나중에 다중 계정을 지원할 수 있으니 미리 대비하는 차원에서 계정 ID를 넣었다.

open class TweetDBObject(open var userId: Long = -1, @PrimaryKey open var tweetId: Long = -1) :
    RealmObject()

새로 로드되는 모든 트윗은 데이터베이스에 저장되어야 한다. DB 관리를 쉽게 하기 위해 트윗 로드 관련 작업을 하나의 객체에 몰아줄 필요가 생겼는데, 나는 싱글턴 패턴을 사용하였다.

object TwitterAPI {
    // operations...
}

코틀린에서는 ``object`` 키워드로 싱글턴 객체를 만들 수 있다. 이제 ``getInstance()`` 안 써도 된다!

타임라인 로드

트위터 API에서 홈 타임라인을 불러와 보여준다. 성능을 고려하면 비동기적으로 불러오는 게 맞지만, 일단 테스트를 위해 동기 메소드를 사용한다.

 

그런데 API 1.1에서는 트윗에 달린 답글의 수를 직접 알 수 없다. ``reply_id``로 직접 검색해야 하는데, 검색을 여러 번 하다 보니 금방 리밋에 걸린다. 실제로 사용하는 상황이 아니라 개발하면서 테스트하는 도중에도 리밋이 걸려서, 결국 API v2를 직접 구현하기로 했다.

Q. 그냥 라이브러리 쓰면 되지 않나?

지금 쓰고 있는 ``Twitter4J``는 2년 전에 개발이 중단된 상태이다. 다른 라이브러리가 있지만 하나는 아직 개발 중이고 하나는 프로젝트 import가 안 돼서.. ㅠㅠ

 

그런데 지금 API v2는 아직 개발 중이라서, 타임라인 로드는 안 된다. 타임라인을 불러오는 부분만 1.1 버전을 사용하고, 타임라인에 속한 각 트윗 정보는 v2로 불러와야 한다. 일해라 트위터

 

옛날에 전적 앱 만들 때에도 HTTP request를 써 본 경험이 있기에 바로 시작했다.

API v2 구현

API v2에는 여러 기능이 있지만, 일단 지금 필요한 트윗 로드 관련 기능만 구현한다. 다음의 로직을 구현한다.

 

  1. 요청 보내기
  2. 응답을 받아 객체로 파싱하기
  3. 파싱한 결과를 앱에 보여주기

API v2에서는 투표 정보도 받아올 수 있어서, 나중에 투표도 보여줄 수 있도록 ``TweetMiniView``를 수정할 계획이다.

 

``Volley``를 이용하여 요청을 보내고, ``Gson``을 사용하여 json 문자열을 코틀린 객체로 파싱한다. 파싱한 트윗을 ``RecyclerView``에 보여주면 된다.

 

implementation 'com.android.volley:volley:1.1.1'
implementation 'com.google.code.gson:gson:2.8.6'

그런데 ``Volley``는 본질적으로 비동기적이다. 따라서 트윗을 보여주는 부분 역시 비동기적으로 실행되게 해야 하는데, 나는 요청이 성공했을 때 실행할 함수를 API 함수에 인자로 넘겨 주는 방식으로 구현했다. 대략 이런 식이다.

// at TwitterAPI
fun loadTimeline(callback: (TweetsV2) -> Unit) {
        val request = object : StringRequest(Method.GET,
        url,
        Response.Listener {
            val tweets = gson.fromJson(it, TweetsV2::class.java)
            callback(tweets)
        }, Response.ErrorListener {
            it.printStackTrace()
        })
    }
    requestQueue.add(request)
}

// at fragment
TwitterAPI.loadTimeline(::setTimelineRecyclerView)

 

요청이 성공하면 가운데의 ``Response.Listener`` 부분이 실행되는데, 이곳에서 응답을 파싱하여 ``callback``을 실행한다. ``callback``에서는 ``TweetRecyclerView``를 ``Visible``로 바꾸고 어댑터를 설정한다.

성공!

타임라인을 성공적으로 로드했다. 그런데 플텍 계정의 트윗은 로드되지 않아서 찾아보니 OAuth 2.0을 사용하기 때문이라고 한다. 플텍 계정의 트윗까지 로드하려면 HTTP request를 보낼 때 OAuth 1.0a를 사용해서 인증해야 한다. 이 부분이 굉장히 어려워 보여서 일단 TODO로 남겨놓는다.

데이터베이스에서 트윗 로드

트위터 공앱의 동작 원리를 생각해 보면, 데이터베이스에 저장된 트윗을 보여준 다음 최신 타임라인을 로드한다. 

 

  1. 데이터베이스에서 트윗 id를 내림차순으로 최대 20개 로드
  2. 로드한 id로 트윗을 불러와서 보여줌
  3. 최신 타임라인 로드(바로 윗 부분)
Realm.getDefaultInstance().where<TweetObject>().findAll().sort("tweetId", Sort.DESCENDING)

위의 코드를 실행하면 ``RealmResults<TweetObject>``를 얻을 수 있다. ``RealmResults``는 ``List``를 상속했기 때문에 리스트를 사용하는 것처럼 조작하면 된다.

 

그런데 ``RecyclerView``에 최신 타임라인이 추가될 때 데이터베이스에서 로드한 트윗이 밑으로 내려가 버린다. 공앱처럼 기존 트윗은 그대로 있고 그 위에 트윗을 추가해야 하는데, 이 부분 구현이 어려워 최우선 TODO로 남겨놓는다.

 

일단 데이터베이스 트윗+최신 트윗은 제대로 로드된다.

``TweetMiniView`` 수정

원래 ``name``과 ``username``(@로 시작하는 아이디)는 한 줄에 표시했는데, ``username``이 아주 경우 오른쪽 날짜가 안 보이는 불상사가 발생하여.. ``name``과 ``username``을 세로로 표시하도록 수정했다. 

수정 전 → 수정 후

TODO

  • 타임라인을 로드할 때 ``RecyclerView``가 맨 위로 스크롤되지 않게 해야 함
  • OAuth 1.0a 구현
  • 리트윗을 제대로 보여주도록 수정하기
  • ``TweetMiniView``에 기능 추가하기(리트윗, 마음에 들어요 등)

'프로젝트 > MiTweet' 카테고리의 다른 글

MiTweet 개발일지 9  (0) 2020.09.11
MiTweet 개발일지 8  (0) 2020.09.08
MiTweet 개발일지 6  (0) 2020.08.28
MiTweet 개발일지 5  (0) 2020.08.27
MiTweet 개발일지 4  (0) 2020.08.19
Comments