Meteor는 자체적으로 Cordova 를 통한 iOS/Android 앱을 지원한다.
하지만, Hybrid 앱은 역시 Native에 비해 예측 못할 변수가 많고 성능도 그다지 좋지 않다.
DDP를 사용하여 Native Android 앱과 Meteor 서버를 연결해보자.
새 프로젝트를 만들고 File/Project Structure (cmd+;) 으로 들어가서
하단 +를 누르면
하지만, Hybrid 앱은 역시 Native에 비해 예측 못할 변수가 많고 성능도 그다지 좋지 않다.
DDP를 사용하여 Native Android 앱과 Meteor 서버를 연결해보자.
새 프로젝트를 만들고 File/Project Structure (cmd+;) 으로 들어가서
하단 +를 누르면
- Library Dependency
- File Dependency
- Module Dependency
세개 나온다.
Choose Library Dependency 창에서 ddp 하고 엔터쳐보자.
지금 현재는 3개 정도 나오는데
com.keysolutions:android-ddp-client:1.0.2.0
이걸 써서 할거다.
선택하고 OK 해서 추가하자.
activity_main.xml 에 버튼 하나 만들고
AndroidManifest.xml 에
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
이 두 개의 permission 이 있는지 확인하자. 없으면 추가. <application>이랑 나란히 놓으면 된다.
준비 해야할 class 는 일단 두개인데
하나는 어플리케이션 전체에서 DDP 연결을 책임질 MyApplication (Application을 상속받은 클래스)와 DDP에서 subscription 처리를 해줄 LoginDDPState(DDPStateSingleton을 상속받은 클래스)이다.
물론 이름은 임의로 정했다.
먼저 MyApplication.java 를 보자
package com.appsoulute.meteorddpexam1; import android.app.Application; import android.content.Context; import android.content.res.Configuration; public class MyApplication extends Application { private static Context sContext = null; @Override public void onCreate() { super.onCreate(); MyApplication.sContext = getApplicationContext(); initSingletons(); } private void initSingletons() { LoginDDPState.initInstance(MyApplication.sContext, "192.168.0.11", 4100, false); } public static Context getAppContext() { return MyApplication.sContext; } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } @Override public void onLowMemory() { super.onLowMemory(); } }
대략 이런 구조 되시겠다.
복수의 DDP를 사용할 경우도 마찬가지 initSingletons 부분에 Meteor Server 주소, 포트, SSL 사용여부를 initInstance에 넣어주고 LoginDDPState를 구현해주면 되겠다.
만들고 나서 AndroidManifest.xml에 가서 application에 android:name 속성을 추가한다.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.appsoulute.meteorddpexam1"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:name=".MyApplication" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
반드시 자동완성을 사용해서 오타를 피한다.
다음은 LoginDDPState 를 만들어보자.
package com.appsoulute.meteorddpexam1; import android.content.Context; import com.keysolutions.ddpclient.DDPClient; import com.keysolutions.ddpclient.android.DDPStateSingleton; /** * Created by spectrum on 6/14/16. */ public class LoginDDPState extends DDPStateSingleton { private LoginDDPState(Context context, String meteorServer, Integer meteorPort, boolean useSsl) { super(context, meteorServer, meteorPort, useSsl); } public static void initInstance(Context context, String meteorServer, Integer meteorPort, boolean useSsl) { if (mInstance == null) { mInstance = new LoginDDPState(context, meteorServer, meteorPort, useSsl); } } public static LoginDDPState getInstance() { // Return the instance return (LoginDDPState) mInstance; } @Override public void broadcastSubscriptionChanged(String subscriptionName, String changetype, String docId) { if (subscriptionName.equals("getChats")) { if (changetype.equals(DDPClient.DdpMessageType.ADDED)) { } else if (changetype.equals(DDPClient.DdpMessageType.REMOVED)) { } else if (changetype.equals(DDPClient.DdpMessageType.UPDATED)) { } } super.broadcastSubscriptionChanged(subscriptionName, changetype, docId); } }
조금 긴 것 같지만 잘 보면 별거 없다.
MyApplication 에서 호출한 initInstance를 만들어주고
getInstance에 싱글톤인 mInstance를 넘겨주는 것 정도한 뒤
broadcastSubscriptionChanged에서 subscription 처리를 하면 된다.
subscription 명하고 changeType에 따라 Map을 만들거나 해서 처리하면 된다.
이제 문제는 MainActivity인데
그전에 Android 의 LifeCycle을 잠시 언급하면
생성시 : onCreated > onStart > onResume
소멸시 : onPause > onStop > onDestroy
순인 걸 기억하자.
먼저, onResume 에서 할게 좀 많은데 대략 아래와 같다.
protected void onResume() { super.onResume(); LoginBroadcastReceiver(); DDPBroadcastReceiver(); LoginDDPState.getInstance().connectIfNeeded(); // start connection process if we're not connected }
두 개의 처리를 해주는데 DDP상태(LoginDDPState)와 DDPBroadcast(DDPBroadcastReceiver)를 다루는 부분을 처리하면 된다.
마지막으로 LoginDDPState 가 연결이 필요하면 connectIfNeeded()로 연결하도록 하자.
LoginBroadcastReceiver는
private void LoginBroadcastReceiver() { mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras(); if (intent.getAction().equals(LoginDDPState.MESSAGE_ERROR)) { String message = bundle.getString(LoginDDPState.MESSAGE_EXTRA_MSG); } else if (intent.getAction().equals(LoginDDPState.MESSAGE_CONNECTION)) { int state = bundle.getInt(LoginDDPState.MESSAGE_EXTRA_STATE); if (state == LoginDDPState.DDPSTATE.LoggedIn.ordinal()) { // login complete, so we can close this login activity and go back Log.d("Login", "LoginServer Completed"); } } } }; LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(LoginDDPState.MESSAGE_ERROR)); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(LoginDDPState.MESSAGE_CONNECTION)); if (LoginDDPState.getInstance().getState() == LoginDDPState.DDPSTATE.Closed) { showError("Connection Issue", "Error connecting to server.. try again"); } }
이와 같은데 MESSAGE_ERROR와 MESSAGE_CONNECTION을 각각 수신하도록 등록해서 오류처리와 접속 후 상태처리를 해주면 된다.
DDPBroadcastReceiver의 경우는
private void DDPBroadcastReceiver() { DDPBroadcastReceiver mDDPReceiver = new DDPBroadcastReceiver(LoginDDPState.getInstance(), this) { @Override protected void onDDPConnect(DDPStateSingleton ddp) { super.onDDPConnect(ddp); // add our subscriptions needed for the activity here ddp.subscribe("get", new Object[] {}); } @Override protected void onSubscriptionUpdate(String changeType, String subscriptionName, String docId) { if (subscriptionName.equals("get")) { } } @Override protected void onLogin() { // update login/logout action button findViewById(R.id.linearLayoutLogin).setVisibility(View.INVISIBLE); findViewById(R.id.linearLayoutLogout).setVisibility(View.VISIBLE); } @Override protected void onLogout() { // update login/logout action button findViewById(R.id.linearLayoutLogin).setVisibility(View.VISIBLE); findViewById(R.id.linearLayoutLogout).setVisibility(View.INVISIBLE); } }; }
이와 같이 DDP연결 시 처리 - 여기선 get 이라는 이름의 subscribe 를 신청함 - 와 subscription의 갱신, 로그인/로그아웃시 처리를 해주면 된다.
반면, onPause의 경우는 간단한데
@Overrideprotected void onPause() { super.onPause(); if (mReceiver != null) { LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); mReceiver = null; } if (mDDPReceiver != null) { // unhook the receiver LocalBroadcastManager.getInstance(this) .unregisterReceiver(mDDPReceiver); mDDPReceiver = null; } }
이와 같이 onResume 시에 등록했던 Receiver들을 unregisterReceiver 해주면 된다.
그러면 실제 구현을 해보자.
onCreate에 login/logout 버튼을 연결하여보면
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btnLogin = (Button)findViewById(R.id.buttonLogin); if (btnLogin != null) { btnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { LoginDDPState.getInstance().login( ((TextView)findViewById(R.id.editTextUserName)).getText().toString(),((TextView)findViewById(R.id.editTextPassword)).getText().toString() ); }}); } Button btnLogout = (Button)findViewById(R.id.buttonLogout); if (btnLogout != null) { btnLogout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { LoginDDPState.getInstance().logout(); } }); } }
이와 같이 구현할 수 있다.
별로 어려운 건 없다. js 의 경우 callback에 해당하는 Receiver 들만 꼼꼼하게 잘 구현하면 Meteor의 풍족한 서버환경을 어려움 없이 사용할 수 있을 것이다.
참고자료;
댓글
댓글 쓰기