관리 메뉴

나만을 위한 블로그

[Android] 웹뷰에 표시된 Node.js GET 요청을 레트로핏으로 받아오기 본문

Android

[Android] 웹뷰에 표시된 Node.js GET 요청을 레트로핏으로 받아오기

참깨빵위에참깨빵 2022. 2. 3. 22:46
728x90
반응형

안드로이드 웹뷰 통신 시 Node.js 서버에서 JSON 객체를 GET 방식으로 받아와야 할 때가 있다.

예제를 찾아봤는데 HttpURLConnection과 이미 deprecated된 AsyncTask를 사용하는 예제가 대부분이라 레트로핏을 사용한 버전으로 바꿔 올린다.

Node.js 예제 소스는 아래와 같다. 파일명은 app.js다.

 

var express = require('express');
var app = express();

app.set('views', __dirname);
app.set('view engine', 'ejs');
app.engine('html', require('ejs').renderFile);

app.get('/', function(req, res) {
    res.json({id : 1, username : "aaa"});
});

var server = app.listen(8080, function() {
    console.log("8080 포트에서 서버 작동 시작!!");
})

 

이렇게 하고 vs code 기준 터미널에 node app.js를 입력하면 13번 줄의 "8080 포트에서 서버 작동 시작!!" 문구가 터미널에 출력되면서 Node.js 서버가 시작된다.

간단하게 테스트하기 위해 로컬에서 서버를 시작시킨 것이기 때문에 이를 안드로이드에서 레트로핏으로 네트워크 통신하려면 cmd를 열고 ipconfig로 확인한 IPv4 주소로 요청을 보내야 한다. 코드로 확인하자.

 

<uses-permission android:name="android.permission.INTERNET"/>	// 필수
<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:usesCleartextTraffic="true"	// 필수
    android:supportsRtl="true"

 

필수라 주석을 적은 속성과 권한은 무조건 매니페스트에 넣어줘야 한다.

앱 수준 gradle에는 아래와 같이 의존성 문구들을 복붙해 준다.

 

implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

implementation("com.squareup.okhttp3:okhttp:3.4.2")
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.4.2'
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.2'

 

okhttp 관련 의존성 문구는 안드로이드에서 레트로핏 로그를 좀 더 세세하게 보고 싶을 때 사용한다. 필요없다면 이 문구들을 제거하고 아래의 코드들에서 관련 코드들을 없애면 된다.

다음은 레트로핏 객체를 생성하는 ApiClient와 레트로핏에서 사용할 추상 메서드를 담는 ApiInterface 파일이다.

 

import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;

public class ApiClient {
    private static Retrofit retrofit;
    private static final String BASE_URL = "http://xxx.xx.xx.xxx:8080/";

    public static Retrofit getRetrofit() {
        Gson gson = new GsonBuilder()
                .setLenient()
                .create();

        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(setHttpLoggingInterceptor())
                .build();

        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(client)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build();
        }

        return retrofit;
    }

    public static HttpLoggingInterceptor setHttpLoggingInterceptor() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(message -> Log.e("HttpLoggingInterceptor", "message : " + message));
        return interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
    }
}

 

BASE_URL에 넣는 IP는 로컬에서 Node.js 서버를 구동한 경우, 위에서 말한 ipconfig로 확인한 IPv4 주소를 넣어주면 된다. 외부 서버를 사용하는 경우 그 서버에 접근할 수 있는 IP를 넣어주면 된다. 포트는 App.js 코드에 적은 대로 8080을 넣어줬다.

아래는 ApiInterface 파일이다.

 

import retrofit2.Call;
import retrofit2.http.GET;

public interface ApiInterface {
    @GET("/")
    Call<String> getData();
}

 

GET 옆의 String 안에는 적절한 엔드포인트를 넣어주면 된다. 필요하다면 Call 옆의 제네릭도 적절히 바꿔준다.

다음은 MainActivity의 XML과 자바 파일이다.

 

<?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">

    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import androidx.appcompat.app.AppCompatActivity;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

    private final String TAG = this.getClass().getSimpleName();

    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        webView = findViewById(R.id.webview);

        initNiceWebView();
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void initNiceWebView() {
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setDomStorageEnabled(true);
        webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
        webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);

        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                ApiInterface apiInterface = ApiClient.getRetrofit().create(ApiInterface.class);
                Call<String> call = apiInterface.getData();
                call.enqueue(new Callback<String>() {
                    @Override
                    public void onResponse(Call<String> call, Response<String> response) {
                        if (response.isSuccessful()) {
                            Log.e(TAG, "성공 : " + response.body());
                            parseJson(response.body());
                        } else {
                            try {
                                Log.e(TAG, "실패 : " + response.errorBody().string());
                            }   catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }

                    @Override
                    public void onFailure(Call<String> call, Throwable t) {
                        Log.e(TAG, "에러 : " + t.getMessage());
                    }
                });
            }
        });

        webView.loadUrl("http://xxx.xx.xx.xxx:8080/");
    }

    private void parseJson(String json) {
        JSONObject jsonObject;
        try {
            jsonObject = new JSONObject(json);
            String id = jsonObject.getString("id");
            String username = jsonObject.getString("username");
            Log.e(TAG, "파싱한 id : " + id + ", username : " + username);
        }   catch (JSONException e) {
            e.printStackTrace();
        }
    }
}

 

웹뷰로 ApiClient에서 사용한 URL을 넣고 실행하면 웹뷰에 JSON 형태의 문자열이 찍히게 된다.

그리고 서버 통신에 성공하면 JSON 값을 받아서 파싱한 다음 로그로 출력하게 된다. 성공 케이스만 있고 실패 케이스는 없으니 이 로직을 사용한다면 실패 케이스에 대한 예외처리는 별도로 만들어줘야 한다.

에뮬레이터로 실행하면 아래와 같은 화면이 나온다.

 

 

그리고 로그에는 이렇게 찍힌다.

 

 

okhttp 코드까지 모두 적용한다면 이런 로그가 나오게 되는데, 봐야 할 것은 맨 밑의 2줄이다.

성공 시 웹뷰로 표시한 URL에서 JSON 값을 받아오는 걸 볼 수 있고, 이를 파싱해서 변수에 담아 로그로 출력하는 걸 볼 수 있다.

POST의 경우도 @FormUrlEncoded로 요청하든, raw로 요청하든 해서 받아오면 된다. ApiInterface의 내용만 조금 바뀔 뿐이고 그 후의 로직은 똑같다.

 

 

참고한 사이트)

 

https://supdev.tistory.com/13

 

Android로 Node.js 서버에 GET, POST 요청하기

Android MainActivity 부분 1. GET / 데이터 받기 public class MainActivity extends AppCompatActivity { private TextView tvData; @Override protected void onCreate(Bundle savedInstanceState) { super.onC..

supdev.tistory.com

 

반응형
Comments