Daily life with Jetpack Compose part 1

How to achieve simple tasks with Jetpack Compose series. Simple tasks with plain Android stayed mostly the same with minor API changes, but there are no official documentation on this subject.

How to display a web web in a Composable

In your code

import android.webkit.WebView
import android.webkit.WebViewClient

@Composable
fun createWebView(article: Post, modifier: Modifier) {
    val context = ContextAmbient.current
    val webViewClient = WebViewClient()
    AndroidView(
        viewBlock = {
            WebView(context).apply {
                this.webViewClient = webViewClient
                this.loadUrl("https://hanquoc.kr/${article.slug}")
            }
        },
        modifier = modifier
    )
}

Don’t use the androidx version – it’s useless

How to share a URL with Jetpack Compose

In your code

import android.content.Intent

@Composable
fun ArticleView(
    article: Post
) {
    val context = ContextAmbient.current
    val shareIntent = Intent(Intent.ACTION_SEND)
    shareIntent.type = "text/plain"
    shareIntent.putExtra(Intent.EXTRA_TEXT, "https://hanquoc.kr/${article.slug}")
    shareIntent.putExtra(Intent.EXTRA_SUBJECT, article.title.rendered)
    startActivity(
        context,
        Intent.createChooser(shareIntent, null),
        null
    )
}

How to write a C MQTT client using Mosquitto

Introduction

How to write a C MQTT client using Mosquitto The 2018 version, based upon this excellent post by Kevin Boone:

Writing an MQTT client C for ActiveMQ from the ground up

The article above is a good and easy starting point, but it hasn’t been updated for 2 years so when you run it with the latest version of Mosquitto, it doesn’t work – and it’s a bit hacky (using “sleep” to avoid a concurrency problem).

So I analyzed the latest mosquitto_pub code from mosquitto repository itself to see how it’s working, and this article is the result.

What’s changed

  • There’s a queue inside mosquitto, `mosquitto_loop` must be called for it to be processed. Alternatively, you can also use the `mosquitto_loop_start` and `mosquitto_loop_stop`
  • I added asynchronous (callback) processing to wait for calls to complete, instead of the ole’ sleep function
  • It’s 2018! Everyone is adopting HTTPS. Accordingly, your MQTT traffic shouldn’t be left bare for all to see! Let’s use TLS to encrypt the traffic

The code

How to use callback

I want to publish just once message, so my flow is the following

  • On connect complete -> publish a message
  • On publish complete -> start to disconnect
  • On disconnect complete -> exit the loop and return control to the main thread. If you don’t wait for this, data may not even get sent!

To do this, I set up 3 “hooks” (callback function), like this

mosquitto_connect_callback_set(mosq, my_connect_callback);

mosquitto_disconnect_callback_set(mosq, my_disconnect_callback);

mosquitto_publish_callback_set(mosq, my_publish_callback);
And then write the callback functions to execute my flow
void my_connect_callback(struct mosquitto *mosq, void *obj, int result)
{
    int rc = MOSQ_ERR_SUCCESS;
    if(!result){
        printf("Sending message...\n");
        rc = mosquitto_publish(mosq, &mid_sent, MQTT_TOPIC, strlen(text), text, qos, retain);
        if(rc){
            switch(rc){
                case MOSQ_ERR_INVAL:
                    fprintf(stderr, "Error: Invalid input. Does your topic contain '+' or '#'?\n");
                    break;
                case MOSQ_ERR_NOMEM:
                    fprintf(stderr, "Error: Out of memory when trying to publish message.\n");
                    break;
                case MOSQ_ERR_NO_CONN:
                    fprintf(stderr, "Error: Client not connected when trying to publish.\n");
                    break;
                case MOSQ_ERR_PROTOCOL:
                    fprintf(stderr, "Error: Protocol error when communicating with broker.\n");
                    break;
                case MOSQ_ERR_PAYLOAD_SIZE:
                    fprintf(stderr, "Error: Message payload is too large.\n");
                    break;
            }
            mosquitto_disconnect(mosq);
        }
    } else {
        if(result){
            fprintf(stderr, "%s\n", mosquitto_connack_string(result));
        }
    }
}

void my_disconnect_callback(struct mosquitto *mosq, void *obj, int rc)
{
    printf("Disconnected!\n");
    connected = false;
}

void my_publish_callback(struct mosquitto *mosq, void *obj, int mid)
{
    printf("Published!\n");
    if(disconnect_sent == false){
        mosquitto_disconnect(mosq);
        disconnect_sent = true;
    }
}

How to process the queue with mosquitto_loop

In the main trunk of your code, do this

int rc;
do {
//network동작 끝나기 전에 모스키토 동작을 막기위해 잠깐 딜레이가 필요
  rc = mosquitto_loop(mosq, -1, 1);
} while (rc == MOSQ_ERR_SUCCESS && connected);

How to add TLS to the connection process

Before connecting, set TLS options with mosquitto_tls_set

mosquitto_username_pw_set(mosq, MQTT_USERNAME, MQTT_PASSWORD);
mosquitto_tls_set(mosq, "ca-cert.pem", NULL, NULL, NULL, NULL);
int ret = mosquitto_connect(mosq, MQTT_HOSTNAME, MQTT_PORT, 0);

Complete publish – subscribe sample

Available at https://github.com/thanhphu/mosquitto-sample. Happy cloning!

 

Resolving ClassCastException using ADT’s visual designer

Recently when maintaining an old android program I encountered a strange exception when attempting to view a layout in visual designer:

error!
ClassCastException: com.android.layoutlib.bridge.MockView cannot be cast to android.view.ViewGroup
Exception details are logged in Window > Show View > Error Log
The following classes could not be found:
- TableLayout (Change to android.widget.TableLayout, Fix Build Path, Edit XML)

Searching stack overflow and Android developers, I found this could be caused by using an old version SDK for an application targeted to a newer version, with new elements. But TableLayout was available since API level 1, and I’m using the latest version of the development tools, so these could not be the problem. The temporary fix was to remove the table layout and replace it with something else.

Today I have the time to compare my code to Android’s example. It turned out to be one tiny little thing that messed the designer up:

<TableLayout

			android:layout_width="fill_parent"
			android:layout_height="wrap_content"
			android:orientation="vertical"
			android:stretchColumns="">

There, one attribute doesn’t match the schema (it should be 0 or 1, not empty “”), and the designer will refuse to load. Worse still, the code will compile but will crash at run-time for the same reason.

My recommendation? Android team should note this case and provide better details on the loading exception in upcoming versions. “ClassCastException” is not really helpful!

 

Debugging the Galaxy Tab 10.1

When I first plug the tab in, my computer automatically install all the drivers, but it won’t show up in ADB and Eclipse. I did a little searching but people were pointing me to Samsung’s developer page with a USB driver for phones. Sadly that driver just isn’t for the tab, a search on their site found nothing interesting either. It looks like Samsung can’t keep information for developers updated while they are still battling legal issues around the tab.

Fortunately, I remembered I haven’t turned debugging mode on the tab on, so I went ahead and do it, this time… Success!

Where to turn on debugging

Device in Eclipse

Now I’m installing Android 3.1 platform and looking forward for a happy time debugging 🙂

Android 2.3

Has been released with a new set of tools. ADT skipped from version 0.9.9 to 8.0.0, and you will get this:

error: Multiple substitutions specified in non-positional format; did you mean to add the formatted=”false” attribute

If you had something like

<string name="loading">my key %s is associated with a value of %s</string>

The reason is, Android now enforce substitution parameter order, you have to made the above.

<string name="loading">my key %1$s is associated with a value of %2$s</string>

This change is possibly due to internationalization problems, in some languages the second parameter should appear first.