Android Application Example (get)(spark.publish)

Dear Sparksters,

this took me a while and since there is no good tutorial in the wild I decided to write one. Sure, it is not perfect but I hope you can use some of the code. With this you should be able to create your own Android Application and get all what you send via your SparkCore to the Cloud to you phone/tablet. Enjoy.

Spark Core Program
Nothing special, we put some text in a variable and pass it over to the Spark Cloud. Open up https://build.spark.io and in the main.ino file copy+paste the following code:

/*****************************************
 *      _____
 *   __|___  |____
 *  |   ___|      |
 *  |   |__       |
 *  |______|rimson|
 *     |_______|       
 *
 *
 * Author : CrimsonClyde
 * Email  : clyde@darkpack.net
 * Test sketch for an Android application.
*****************************************/

include "application.h"

void setup(){
		Serial.begin(57600);
		Serial.println("------------------Setup---------------------");
		// Code
}

void loop(){
		Serial.println("------------------MainLoop---------------------");
		char foo[64];
		unsigned bar = "Metasyntactic Variable";
		sprintf(foo, "{\"Foobar:  \"}", bar);
		Spark.publish("baz",foo);
		
}

Explanation:
With sprintf we compose a text string into a C variable:
Spark.publish() is used to send the data to the Spark Cloud via the API. There is a very good tutorial out there written by @bko It is highly recommended that you first try this one!

Android Application
Download a fresh copy of the Android Studio and install it. I use currently 1.0.2.
Start a new Project with following settings:

Application Name: MyCoreApp
Company Name: com.something
Minimum SDK: API19: Android 4.4 (KitKat)
Choose: “Blank Activity”
Activity Name: MainActivity
Layout: activity_main
Title: MainActivity
Menu Resource Name: menu_main

Hit

The Layout
Add this to your activity_main.xml file.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MyCoreApp"
        android:id="@+id/headerTextView"
        android:textSize="30sp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Wait... foobar you said?"
        android:id="@+id/foobarTextView"
        android:textSize="24sp"
        android:layout_marginTop="191dp"
        android:layout_below="@+id/headerTextView"
        android:layout_centerHorizontal="true" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Refresh Data"
        android:id="@+id/refreshButton"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true" />
    
    
</RelativeLayout>

Explanation:
This is pretty straight forward, we just add two text views and a button. We assign id´s to them, that we can use them later in our project.

Manifest
We need internet permissions for this. No internet no connection, no stream no dream. Therefore open the AndroidManifest.xml and delete everything inside and replace it with this:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="something.com.mycoreapp" >
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Helper Class
Now let´s dive deep into the world of Java and add a new .java File to your project. On the left side of Android Studio you can see your App (Android should be selected) and under the “java” section you can find your MainActivity file. With a simple right-click on the “something.com.mycoreapp” we choose New -> Java Class and name it “Data”. Delete everything in the file and copy the following content to it.
package something.com.mycoreapp;

  /**
 * _________        .__
 * \_   ___ \_______|__| _____   __________   ____
 * /    \  \/\_  __ \  |/     \ /  ___/  _ \ /    \
 * \     \____|  | \/  |  | |  \\___ (  <_> )   |  \
 *  \______  /|__|  |__|__|_|  /____  >____/|___|  /
 *         \/                \/     \/           \/
 * Project: CrimsonControl (CTRL)
   *
   * Author : CrimsonClyde
   * E-Mail : clyde_at_darkpack.net
   * Thx    : 4nt1g, Seelenfaenger, Bonnie
   * Use at your own risk! Keep Mordor tidy
 */
    
    public class Data {
     private String foobar = null;
    
     public boolean isReady() {
     return (foobar != null);
     }
    
     public Integer getFoobar() { return foobar; }
     
     public void setFoobar(Integer foobar) { this.foobar = foobar; }
    
     @Override
     public String toString() {
     return "Data [Foobar=" + foobar + "]";
     }
    }

ActionBar
Get rid of the ActionBar, we don´t need it for this project. So open up “values/styles.xml” and change the content of the file to this:

<resources>

 <!-- Base application theme. -->
 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
 <!-- Customize your theme here. -->
 </style>

</resources>

Explanation:
See that we use “Theme.AppCompat.Light.NoActionBar”? With that little change we became rid of the ActionBar at the top of our MainActivity.
If you want to keep it, try out by your own, but don´t forget that you have to add the following code inside your MainActivity class! But we do not need it for this example here.

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}

The MainActivity
Awesome, we have constructed a little helper class and the layout is also ready for use. You´re doing great my young Padawan! The Force is what gives a Developer his power. It’s an energy field created by all bits of code. It surrounds us and penetrates us. It binds the code together.
Concentrate now, this is where the magic happens. Open up your MainActivity.java file and delete/copy/paste.

package something.com.mycoreapp;

  /**
 * _________        .__
 * \_   ___ \_______|__| _____   __________   ____
 * /    \  \/\_  __ \  |/     \ /  ___/  _ \ /    \
 * \     \____|  | \/  |  | |  \\___ (  <_> )   |  \
 *  \______  /|__|  |__|__|_|  /____  >____/|___|  /
 *         \/                \/     \/           \/
 * Project: CrimsonControl (CTRL)
 *
 * Author : CrimsonClyde
 * E-Mail : clyde_at_darkpack.net
 * Thx    : 4nt1g, Seelenfaenger, Bonnie
 * Use at your own risk! Keep Mordor tidy
 */ 

import android.os.AsyncTask;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;

public class MainActivity extends ActionBarActivity {
    // Set MainActivity as TAG for our debug log
    public static final String TAG = HttpsClient.class.getSimpleName();

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

        // Declare & Assign
        final Button updateButton = (Button) findViewById(R.id.refreshButton);

        // OnClick Listener
        View.OnClickListener listener = new View.OnClickListener() {

            @Override
            public void onClick(View v) {

                // Update the <> with your CoreID and your AccessToken from https://build.spark.io
                new HttpsClient().execute("https://api.spark.io/v1/devices/<MYCOREID>/events/?access_token=<MYACCESSTOKEN>");

            }


        };
        updateButton.setOnClickListener(listener);

    }

    // HttpsClient Class
    class HttpsClient extends AsyncTask<String, Void, String> {

        private Exception exception;
        public String doInBackground(String... urls) {

            try {
                Log.d(TAG, "*******************    Open Connection    *****************************");
                URL url = new URL(urls[0]);
                Log.d(TAG, "Received URL:  " + url);

                HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
                Log.d(TAG, "Con Status: " + con);

                InputStream in = con.getInputStream();
                Log.d(TAG, "GetInputStream:  " + in);

                Log.d(TAG, "*******************    String Builder     *****************************");
                String line = null;
                BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));


                Data data = new Data();

                while ((line = br.readLine()) != null) {
                    if (line.contains("event")) {
                        //do nothing since the event tag is of no interest
                        Log.d(TAG, "Hello darkness my old friend.");
                        continue;
                    }
                    if (line.contains("data: ")) {
                        //Convert to JSON (stripping the beginning "data: "
                        JSONObject jObject = new JSONObject(line.substring(6));
                        String json_data = (String) jObject.get("data");
                        //Convert again
                        jObject = new JSONObject(json_data);

                        //Reading foobar
                        if (jObject.has("Foobar")) {
                            data.setFoobar(jObject.getInt("Foobar"));
                        }

                    }
                    //Check if we have all needed data
                    if (data.isReady()) {
                        //exit endless connection
                        Log.d(TAG, "*******************    Data converted     *****************************");
                        Log.d(TAG, "data:  " + data);

                        break;
                    }
                }


                // Creating finalized containers for UI usage
                final String gFoobar = data.getFoobar() + ", and that´ the reason why!";


                // To access the findViewById we need this to runOnUiThread
                runOnUiThread(new Runnable(){
                    public void run() {
                        final TextView updateText  = (TextView) findViewById(R.id.foobarTextView);
                        Log.d(TAG, "gFoobar:  " + gFoobar);
                        Log.d(TAG, "*******************    Update TextView       *****************************");
                        updateText.setText(gFoobar);
                    }
                });
                Log.d(TAG, "*******************  Stream closed, exiting     *****************************");
                br.close();
            } catch (Exception e) {
                this.exception = e;
                return null;
            }
            return null; 
        }
    }
}

Explanation:
We create a OnClickLister for our button, where we pass the URL including your CoreID and your AccessToken to the HttpsClient class which opens the connection to the Spark Cloud Api. Now we are able to read the Server Side Events from the stream. We simple cache the output and parse it by using JSONObject. The helper class check the content we need is inside that Object and returns us the result. After that we update our TextView.
Simple as that :wink: Hope it works (can´t test it currently, waiting for my Photon)

May the force be with you!

6 Likes

Deprecated

Use this instead:

But how can we update the value in JSON into TextView every 1 second ?