這篇要來寫寫關於Android開發中使用GraphQL連線請求
在寫程式的過程中,蠻多人應該已經對於串接網路http API這件事...沒用過至少也聽過吧XD
那今天文章~~寫點比較特別的
首先,我想問問各位
請問你聽過安麗嗎
欸不是!!並不是!!ಠ_ಠ
請問你聽過GraphQL嗎?
GraphQL是什麼?
根據維基百科...
GraphQL是一個開源的,面向API而創造出來的資料查詢操作語言以及相應的執行環境
於2012年仍處於Facebook內部開發階段,直到2015年才公開發布
2018年11月7日,Facebook將GraphQL專案轉移到新成立的GraphQL基金會(隸屬於非營利性的Linux基金會)
用白話文講,他是一種API傳輸方式
有別於傳統的Http REST 傳輸協定,他是一種可以自由選擇需要的資料,並能以最低資料量達到最高傳輸效果的傳輸方式
恩?你說你還是看不懂?好吧,我們來看看官網以及稍後的範例就能窺知一二了
GraphQL官方網站
英文版->https://graphql.org/
中文版->https://graphql.bootcss.com/
那麼來到今天的主題,本篇我將在Android上使用GraphQL傳輸,來獲取Matters馬特市的一些資料
關於馬特市Matters,我在這篇有寫,大家可以去看看
碼農日常-『LikeCoin』讚賞LikeCoin是什麼?我的LikeCoin要如何兌現呢?
來看看Gif範例吧
以及Github
->https://github.com/thumbb13555/GraphQLDemo/tree/master
1. 關於Matters 開源API
Matters是一個部落格平台,上面每天都有很多文章產出
但是跟傳統部落格不一樣的是,Matters除了原始碼開放之外,他也有官方的API可以用!
那麼,就來一探究竟吧
首先,官方提供的API Github在此
->https://github.com/thematters/developer-resource
然後,這裡是GraphQL的Playground
->https://server.matters.news/playground
再來,我們來看一下Playground,玩玩看他的API吧!
首先是介面
左邊的部分是輸入GraphQL指令,右邊則是輸出結果
此外,他還有文檔可以看,非常清楚
左下角部分,還有Query Variables跟Http Headers可以用(這裡要往上拉一下..)
那麼,讓我們來輸入看看今天的API吧,請輸入以下內容
query GetRecommend($cursor:String!) {
viewer {
recommendation {
newest(input: { after:$cursor }) {
totalCount
edges {
node {
title
id
}
}
}
}
}
}
並在下方Query Variables輸入以下
最後,按下中間的
看看結果
恩~如果你真的跟著我打的話,這些資料是他們Matters團隊用於測試的資料
如果想看到真實資料,請把這裡的網址改為https://server.matters.news/graphql就可以了
好的,會操作GraphQL的Playground之後,那接下來就要進入正題囉~
2. 載入GraphQL Plugs以及Apollo第三方資源庫
這一段開始會稍微比較複雜一點,每個步驟可都要看清楚啦~
在Android中,由於GraphQL的結構較為特殊,所以我在這邊也是建議使用Apollo這個第三方函式庫去完成
而Apollo函式庫他其實也是基於okHttp再加以修改,所以在做傳輸時會發現跟okHttp的寫法非常接近
我的網誌之前也有okHttp的資料,可以參考一下
->碼農日常-『Android studio』以okHttp第三方庫取得網路資料(POST、GET、WebSocket)
2-1 載入Apollo第三方函式庫
那麼,我們開始載入Apollo函式庫
這個Apollo函式庫算是很貼心,他還直接告訴你Java和Kotlin怎麼寫
在這->https://www.apollographql.com/docs/android/essentials/get-started-java/
以及這是Apollo的Github
->https://github.com/apollographql/apollo-android
...好,我知道你看官網大概霧煞煞
看我的可能比較快XD
首先,我們開啟好專案後,來到
點build.gradle
然後加入以下內容
plugins { id 'com.android.application' id("com.apollographql.apollo").version("2.5.9") } android { compileSdkVersion 29 buildToolsVersion "29.0.3" defaultConfig { applicationId "com.noahliu.graphqldemo" minSdkVersion 23 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation 'com.apollographql.apollo:apollo-runtime:2.5.9' }
這邊補充一下,如果你是用Kotlin撰寫的話
那要加的如下
plugins { id 'com.android.application' id 'kotlin-android' id("com.apollographql.apollo").version("2.5.9") } android { compileSdkVersion 30 defaultConfig { applicationId "com.example.apollotry2" minSdkVersion 23 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.5.0' implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' implementation("com.apollographql.apollo:apollo-runtime:2.5.9") } apollo { generateKotlinModels.set(true) }
一樣,按下Sync Now,編譯過就是ok囉!
至於如果有編譯不過的話...應該是沒有啦QQ
我一開始一直編譯不過,後來我改成目前這樣才過的
實測是沒問題的啦!
2-2 插入JS GraphQL Plugins
接下來,由於Android studio IDE本身沒有支援.graphql檔案格式
因此我們要插入Plugs,讓他支援
首先點Android Studio->Preferences
找到Plugins,按下Install
ok,到這邊就完成插件了,稍後我們在創建文件的時候,就能夠正常打開囉!
3. 下載Schema.json
好,我們來到了最重要的一個步驟了
GraphQL他之所以可以在Playground顯示,是因為他背後有一個Schema檔案在撐腰
也因此我們現在就要來下載這個檔案
那在這之前,我們必須要創建專屬於GraphQL的資料夾
準備好了嗎?前面的步驟要先過編譯才能接下來這個步驟喔!
那,開始吧
3-1 創建相同路徑資料夾
首先,請先把資料夾顯示模式更換為Project
接下來,找到main資料夾,在底下new->Directory
恩~總之就是一個創建資料夾的過程
重點是要跟要跟java資料夾底下的路徑一模一樣的名字!
像這樣
ok,接下來要來下載schema了
3-2 下載Schema
這裡也是研究很久的部分(嘆
我當時在下載的時候,我看的是官方的介紹
->https://www.apollographql.com/docs/android/tutorial/02-add-the-graphql-schema/
可是...我當時看了半天...看不懂啊!!!!(抱頭
所以後來我找了很多資料,好不容易找到這篇發問
才成功處理完這個問題
那麼,我們就來下載他吧
首先,請到目標資料夾上面,右鍵->Open in Termial
接下來就會看到這個畫面(此方法Windows通用)
然後輸入這一串
npm install -g apollo
按Enter讓他跑
出現這樣表示載入完成
此外,在Mac會有權限不足的問題
missing write access to /usr/local/lib/node_modules mac
這時候開啟終端機
輸入 sudo chown -R $USER /usr/local/lib/node_modules 後,打個密碼即可
再來下載json,請接著輸入這一串
apollo client:download-schema --endpoint=https://server.matters.news/graphql schema.json
同樣Enter給他跑,跑好如下
OK,最後檢查一下資料夾內是否多了Schema.json檔案
若有的話,就是成功囉
有跑成功但是沒有顯示檔案,可以重新選許一下資料夾,有時候是他的UI沒有跑出來的緣故
稍微總結一下該輸入的內容
總之終端機的部分就是輸入以下兩串
npm install -g apollo
apollo client:download-schema --endpoint=https://server.matters.news/graphql schema.json
即可成功載入!
4. 撰寫 .graphql檔案
接下來要來撰寫GraphQL檔案,也就是每個REST檔案
這些檔案我的建議是都先在Playground先寫好,測試沒問題後再複製過來是比較好的作法
這次我的目標是要抓出馬特市的總用戶數,因此我在Playground測試是使用以下這串
query GetTotalUsers($cursor:String!){
oss{
users(input:{after:$cursor}){
totalCount
pageInfo{
startCursor
endCursor
hasPreviousPage
hasNextPage
}
edges{
node{
displayName
info{
createdAt
}
}
}
}
}
}
接著,我創立一個graphql檔案,請在資料夾上方點右鍵->new->GraphQL File
然後把剛剛那一串貼上去(我的檔名GetTotalUsersInMatters.graphql)
會滿江紅!!
但不用擔心,這是正常現象
新增完後,請按Build->Rebuild Project
這步驟非常、非常重要
如果API有改或有新增的情況下,做完更動後每次都要Rebuild一次,這樣程式能編譯,並找出他的檔案
5. 建立連線
來到最後一步驟了,寫連線程式
到這邊為止,必須確認前面都有做到定位後,這裡才能確實執行
然後...其實這部分很簡單XD而且官方網站其實有給你範例!
在這裡
->https://www.apollographql.com/docs/android/essentials/get-started-java/
那麼我照著範例,做出今天的程式
介面
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/textview_Respond" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="24sp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textview_Respond" /> </androidx.constraintlayout.widget.ConstraintLayout>
public class MainActivity extends AppCompatActivity { public static final String TAG = MainActivity.class.getSimpleName() + "My"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApolloClient apolloClient = ApolloClient.builder() .serverUrl("https://server.matters.news/graphql") .okHttpClient( new OkHttpClient.Builder().addInterceptor(new AuthorizationInterceptor("")) .build() ) .build(); Button btClick = findViewById(R.id.button); btClick.setOnClickListener(v -> { getMattersUserInfo(apolloClient); }); } /***/ private void getMattersUserInfo(ApolloClient apolloClient){ apolloClient.query(new GetTotalUsersQuery("")) .enqueue(new ApolloCall.Callback<GetTotalUsersQuery.Data>() { @SuppressLint("SetTextI18n") @Override public void onResponse(@NotNull Response<GetTotalUsersQuery.Data> response) { runOnUiThread(()->{ TextView textView = findViewById(R.id.textview_Respond); String totalCount = String.valueOf(response.getData().oss.users.totalCount); textView.setText("馬特市總人口為: "+totalCount); }); } @Override public void onFailure(@NotNull ApolloException e) { } }); } /**Mutate 範例*/ private void mutateExample(ApolloClient apolloClient){ apolloClient.mutate(new ReadArticleMutation("QXJ0aWNsZToxNTkxOTg")) .enqueue(new ApolloCall.Callback<ReadArticleMutation.Data>() { @Override public void onResponse(@NotNull Response<ReadArticleMutation.Data> response) { Log.d(TAG, "onResponse: " + response.getData().readArticle.title); } @Override public void onFailure(@NotNull ApolloException e) { } }); } /**Query範例*/ private void queryExample(ApolloClient apolloClient) { apolloClient.query(new GetRecommendQuery()) .enqueue(new ApolloCall.Callback<GetRecommendQuery.Data>() { @Override public void onResponse(@NotNull Response<GetRecommendQuery.Data> response) { Log.d(TAG, "onResponse: " + response.getData().viewer.recommendation.newest.totalCount); } @Override public void onFailure(@NotNull ApolloException e) { } }); } /**新增Header*/ private static class AuthorizationInterceptor implements Interceptor { private String token; public AuthorizationInterceptor(String token) { this.token = token; } @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request request = chain.request().newBuilder() .addHeader("x-access-token", token) .build(); return chain.proceed(request); } } }
好,講重點
首先,先宣告ApolloClient型別
ApolloClient apolloClient = ApolloClient.builder() .serverUrl("https://server.matters.news/graphql") .okHttpClient( new OkHttpClient.Builder().addInterceptor(new AuthorizationInterceptor("")) .build() ) .build();
再來是回傳
apolloClient.query(new GetTotalUsersQuery("")) .enqueue(new ApolloCall.Callback<GetTotalUsersQuery.Data>() { @SuppressLint("SetTextI18n") @Override public void onResponse(@NotNull Response<GetTotalUsersQuery.Data> response) { runOnUiThread(()->{ TextView textView = findViewById(R.id.textview_Respond); String totalCount = String.valueOf(response.getData().oss.users.totalCount); textView.setText("馬特市總人口為: "+totalCount); }); } @Override public void onFailure(@NotNull ApolloException e) { } });
特別注意,這裡的GetTotalUsersQuery不是對應檔案名稱,是對應檔頭名稱(在我的Github程式中,我刻意寫不一樣的名字就是為了證實這點)
OK,做到這邊,不知道你是否也成功接收到資料了呢?
這篇GraphQL老實說我真的搞很久才搞定..(哎,程度差,努鈍的我QQ)
會寫這個,真的是純粹因為好奇XD
因為這東西算是蠻新的,用的公司目前還不是很多
像Matters這樣子願意開源讓大家去撈資料,真的是只能說非常非常的感謝
當然,同時我自己也是Matters作者,也一直再以自己的方式回饋這個社群..包含今天的文章也是其中一部分
而今天的文對我來說也是得來不易,畢竟是真的在嘗試很多錯誤後得到的,也是希望大家看我的文章後可以一次完成,不要走歪路了QQ
那嘛,文章就到這邊,如果覺得我文章不錯..
留言列表