diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 7514ec8..68f976f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,31 @@ -# socketcluster-client-android Native Android client for SocketCluster -This project is working in progress, please read ``MainActivity.java`` for existing usages, -feedbacks are welcome. +Please read MainActivity.java for existing usages, feedbacks are welcome. + +Project on basis ilani project. Updated to SocketCluster client version 4.3. As for now android is able to handle most of events and methods from scSocket client. + +Ive been trying to make it as simplest as i can to handle messages at activity. Refactoring existing code, which was very usefull and most of this client is based on forked code, is necessary to keep this client connected to server even if application is in background. To achieve this android client is working as service. + +Implementing this client in application is very simple. Somwhere in app, probably in main activity, implement BroadcastReceiver: + + private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + String event = intent.getStringExtra("event"); + String data = intent.getStringExtra("data"); + handleEvents(event,data); //this line is optional + } + }; +And thats it. From this moment you can handle each event sent from server. Of course its also possible to implement BroadcastReceiver as new class. + +Benchmark button. I wanted to check how efficient is android client, not very efficient but is dispatching all messages even if its many of them in short time. This is code i used in worker: + + socket.on('benchmark', function(data){ + for(n=0;n<500;n++){ + socket.emit('rand', { + rand: n + }); + console.log('emit' + n); + } + }); diff --git a/app/app.iml b/app/app.iml index c465029..31c9524 100644 --- a/app/app.iml +++ b/app/app.iml @@ -1,5 +1,5 @@ - + @@ -65,34 +65,27 @@ - - - - - + + - - - - - - + + - + - - - + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 55256f0..5f2880e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,14 +18,15 @@ allprojects { dependencies { - compile fileTree(include: ['*.jar'], dir: 'libs') - compile 'com.android.support:appcompat-v7:22.1.1' + compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':socketclusterandroidclient') + compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:support-v4:23.1.1' } android { - compileSdkVersion 21 - buildToolsVersion "22.0.1" + compileSdkVersion 23 + buildToolsVersion '23.0.2' defaultConfig { applicationId "socketcluster.io.androiddemo" minSdkVersion 19 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 60952eb..9f8f960 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,11 +1,15 @@ + + - + + + + - + \ No newline at end of file diff --git a/app/src/main/java/socketcluster/io/androiddemo/MainActivity.java b/app/src/main/java/socketcluster/io/androiddemo/MainActivity.java index 558f8f7..e548652 100644 --- a/app/src/main/java/socketcluster/io/androiddemo/MainActivity.java +++ b/app/src/main/java/socketcluster/io/androiddemo/MainActivity.java @@ -1,78 +1,196 @@ package socketcluster.io.androiddemo; + +/** + * Dariusz Krempa + */ +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import android.os.IBinder; +import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; +import android.widget.TextView; + +import com.fangjian.WebViewJavascriptBridge; + +import org.json.simple.JSONValue; -import socketcluster.io.socketclusterandroidclient.ISocketCluster; -import socketcluster.io.socketclusterandroidclient.SocketCluster; +import java.util.HashMap; +import java.util.Map; +import io.socketcluster.socketclusterandroidclient.SCSocketService; -public class MainActivity extends AppCompatActivity implements ISocketCluster { - private SocketCluster sc; +public class MainActivity extends Activity { + private static String TAG = "SCDemo"; + private SCSocketService scSocket; + private Boolean bound = false; + private String options; + private boolean logout; + private TextView textView; + private String authToken; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - sc = new SocketCluster("192.168.199.103", "8000", false, this); - sc.setDelegate(this); - // Connect button - final Button connectBtn = (Button) findViewById(R.id.btnConnect); - connectBtn.setOnClickListener(new View.OnClickListener() { + // Check active subscriptions + final Button subsBtn = (Button) findViewById(R.id.activeSubsBtn); + textView = (TextView) findViewById(R.id.textView); + subsBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + scSocket.subscriptions(true, new WebViewJavascriptBridge.WVJBResponseCallback() { + @Override + public void callback(String data) { + handleEvents("subscriptions", data); + } + }); + } + }); + Map map = new HashMap(); + + String host = "example.com"; + String port = "3000"; + map.put("hostname", host); + map.put("port", port); + options = JSONValue.toJSONString(map); + + // Get connection state button + final Button stateBtn = (Button) findViewById(R.id.stateBtn); + stateBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - sc.connect(); + scSocket.getState(new WebViewJavascriptBridge.WVJBResponseCallback() { + @Override + public void callback(String data) { + handleEvents("getState", data); + } + }); + } + }); + // Benchmark + final Button benchmark = (Button) findViewById(R.id.benchmark); + benchmark.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + scSocket.emitEvent("benchmark", "start"); } }); // Disconnect button - final Button disconnectBtn = (Button) findViewById(R.id.btnDisconnect); + final Button disconnectBtn = (Button) findViewById(R.id.disconnectBtn); disconnectBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - sc.disconnect(); + scSocket.disconnect(); + logout = true; } }); - // Listen to Rand event button handler - final Button listenToRandBtn = (Button) findViewById(R.id.btnListenRand); - listenToRandBtn.setOnClickListener(new View.OnClickListener() { + // Login + final Button loginBtn = (Button) findViewById(R.id.loginBtn); + loginBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - sc.registerEvent("rand"); + scSocket.emitEvent("login", "test"); } }); - - final Button subToWeatherBtn = (Button) findViewById(R.id.btnSubWeather); + // Subscribe to WEATHER channel + final Button subToWeatherBtn = (Button) findViewById(R.id.subBtn); subToWeatherBtn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View view) { - sc.subscribeToChannel("WEATHER"); + String channel = "WEATHER"; + scSocket.subscribe(channel); } }); - final Button unSubToWeatherBtn = (Button) findViewById(R.id.btnUnSubWeather); + //Unsubscribe WEATHER channel + final Button unSubToWeatherBtn = (Button) findViewById(R.id.unsubsBtn); unSubToWeatherBtn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View view) { - sc.unsubscribeFromChannel("WEATHER"); + String channel = "WEATHER"; + scSocket.unsubscribe(channel); } }); - - final Button pubToWeatherBtn = (Button) findViewById(R.id.btnPubWeather); + // Publish to WEATHER channel + final Button pubToWeatherBtn = (Button) findViewById(R.id.pubBtn); pubToWeatherBtn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View view) { - sc.publishToChannel("WEATHER", "CLOUDY"); + String msg = "publish to channel"; + scSocket.publish("WEATHER", msg); } }); + } + + private ServiceConnection conn = new ServiceConnection(){ + + @Override + public void onServiceConnected(ComponentName component, IBinder binder){ + SCSocketService.SCSocketBinder scSocketBinder = (SCSocketService.SCSocketBinder) binder; + scSocket = scSocketBinder.getBinder(); + scSocket.setDelegate(MainActivity.this); + bound = true; + } + + @Override + public void onServiceDisconnected(ComponentName component){ + bound = false; + } + }; + /** + * Bind service is required to access methods in SCSocketService + * startService is required, even if service is bound, to keep SCSocketCluster alive when activity isn't foreground app + * this let to stay application connected to server and receive events and subscribed messages + */ + @Override + protected void onStart(){ + super.onStart(); + Intent intent = new Intent(this, SCSocketService.class); + bindService(intent, conn, Context.BIND_AUTO_CREATE); + startService(intent); + } + @Override + protected void onStop(){ + super.onStop(); + if(bound){ + unbindService(conn); + bound = false; + } } + + @Override + protected void onResume(){ + super.onResume(); + LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, + new IntentFilter("io.socketcluster.eventsreceiver")); + } + + @Override + protected void onPause(){ + LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver); + super.onPause(); + } + + @Override + protected void onDestroy(){ + super.onDestroy(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. @@ -95,42 +213,116 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } - @Override - public void socketClusterReceivedEvent(String name, String data) { - Log.i(TAG, "ReceivedEvent " + name); - Log.i(TAG, "ReceivedEvent " + data); - } + /** + * BroadcastReceiver to receive messages from SCSocketClusterService to handle events + * Broadcast receiver can be changed or even implemented at new class but has to be to handle events from socketcluster client + */ + private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() { - @Override - public void socketClusterChannelReceivedEvent(String name, String data) { - Log.i(TAG, "socketClusterChannelReceivedEvent " + name + " data: " + data); - } - @Override - public void socketClusterDidConnect() { - Log.i(TAG, "SocketClusterDidConnect"); - } - @Override - public void socketClusterDidDisconnect() { - Log.i(TAG, "socketClusterDidDisconnect"); - } - @Override - public void socketClusterOnError(String error) { - Log.i(TAG, "socketClusterOnError"); - } - @Override - public void socketClusterOnKickOut() { - Log.i(TAG, "socketClusterOnKickOut"); - } - @Override - public void socketClusterOnSubscribe() { - Log.i(TAG, "socketClusterOnSubscribe"); - } - @Override - public void socketClusterOnSubscribeFail() { - Log.i(TAG, "socketClusterOnSubscribeFail"); - } - @Override - public void socketClusterOnUnsubscribe() { - Log.i(TAG, "socketClusterOnUnsubscribe"); + @Override + public void onReceive(Context context, Intent intent) { + String event = intent.getStringExtra("event"); + String data = intent.getStringExtra("data"); + handleEvents(event,data); + } + }; + + + public void handleEvents(String event, String data) { + switch(event){ + + default: + textView.append(event + ": " + data + "\n"); + break; + + case SCSocketService.EVENT_ON_READY: + scSocket.connect(options); + textView.append(event + ": " + data + "\n"); + Log.d(TAG, "ready"); + break; + + case SCSocketService.EVENT_ON_CONNECT: + scSocket.emitEvent("login", "Test Driver", new WebViewJavascriptBridge.WVJBResponseCallback() { + @Override + public void callback(String data) { + textView.append("callback: "+data+"\n"); + } + }); + + scSocket.registerEvent("rand"); + textView.append(event+": "+data+"\n"); + Log.d(TAG, "connected: "+data); + break; + + case SCSocketService.EVENT_ON_DISCONNECT: + if(!logout) + scSocket.authenticate(authToken); + + textView.append(event+": "+data+"\n"); + Log.d(TAG, "disconnected"); + break; + + case SCSocketService.EVENT_ON_EVENT_MESSAGE: + textView.setText(event + ": " + data + "\n"); + + Log.d(TAG, "onEvent: "+data); + break; + + case SCSocketService.EVENT_ON_SUBSCRIBED_MESSAGE: + + textView.append(event + ": " + data + "\n"); + Log.d(TAG, "subscribed message: "+data); + break; + + case SCSocketService.EVENT_ON_AUTHENTICATE_STATE_CHANGE: + + textView.append(event+": "+data+"\n"); + Log.d(TAG, "authStateChanged: "+data); + break; + + case SCSocketService.EVENT_ON_SUBSCRIBE_STATE_CHANGE: + + textView.append(event+": "+data+"\n"); + Log.d(TAG, "subscribeStateChanged: "+data); + break; + + case SCSocketService.EVENT_ON_ERROR: + + textView.append(event+": "+data+"\n"); + Log.d(TAG, "error: "+data); + break; + + case SCSocketService.EVENT_ON_SUBSCRIBE_FAIL: + + textView.append(event+": "+data+"\n"); + Log.d(TAG, "subscribeFailed: "+data); + break; + + case SCSocketService.EVENT_ON_AUTHENTICATE: + authToken = data; + textView.append(event+": "+data+"\n"); + Log.d(TAG, "authenticated: "+ authToken); + break; + + case SCSocketService.EVENT_ON_DEAUTHENTICATE: + + textView.append(event+": "+data+"\n"); + Log.d(TAG, "error: "+data); + break; + + case SCSocketService.EVENT_ON_SUBSCRIBE: + + textView.append(event+": "+data+"\n"); + Log.d(TAG, "error: "+data); + break; + + case SCSocketService.EVENT_ON_UNSUBSCRIBE: + + textView.append(event+": "+data+"\n"); + Log.d(TAG, "error: "+data); + break; + + } } + } diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml new file mode 100644 index 0000000..2a6a65b --- /dev/null +++ b/app/src/main/res/layout-land/activity_main.xml @@ -0,0 +1,90 @@ + + + +