📜 ⬆️ ⬇️

Developing an Android widget to display a bitcoin course

Hi, hablochiteli! Background: more recently, I track the growth of bitcoin cryptocurrency (BTC). Previously, to view the quotes, I looked at the site of some stock exchange, but it is much more convenient to have a small widget on the working screen of a smartphone that displays actual information.

On the market there are a lot of various widgets showing cryptocurrency rates. But much more interesting to create something of their own. In this article I will briefly describe my experience of creating a widget for a gadget running the Android OS.

To understand the material of the article, it is advisable to get acquainted with Java and Android development in IDE Android Studio. If not, go here: Android developers .

My small widget will display the price of bitcoin in dollars and the time for which this price is relevant. The information will be downloaded from the BTC-e Russian-language stock exchange, since this platform delivers the course in a convenient JSON format in response to a get request by url btc-e.nz/api/3/ticker/btc_usd .
')
Sample exchange response:

{"btc_usd":{"high":880,"low":836,"avg":858,"vol":3774491.17766,"vol_cur":4368.01172,"last":871.999,"buy":872,"sell":870.701,"updated":1482754417}} 

So, to start developing we create a new project in the IDE with the help of a suitable template. Select “Start a new Android Studio project”, then enter the name and location of the project, select the target device and API version, then you need the “Add no activity” option.

Screenshots
image
image
image

After IDE workspace opens, create an empty widget using the built-in templates. To do this, in the project file tree open the context menu on the app folder and select New → Widget → App Widget.

Screenshot
image

Several files will be created, of which three are particularly interesting.

The first is the xml file (res → xml → btcwidget_info.xml) with the main parameters of the widget:

 <source lang="xml"><?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialKeyguardLayout="@layout/btcwidget" android:initialLayout="@layout/btcwidget" android:minHeight="40dp" android:minWidth="110dp" android:previewImage="@drawable/example_appwidget_preview" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="180000" android:widgetCategory="home_screen"></appwidget-provider> 

The initialLayout parameter specifies the name of the xml file with the visual layout of the widget. minHeight and min-Width - the minimum size of the added widget, updatePeriodMillis - information update time in ms, but not more than once every half hour (the 10 ms parameter is still perceived as the minimum 30 minutes).

The second xml file (res → layout → btcwidget.xml) contains the widget's visual display parameters (layout of visual elements).

It contains a description of a single TextView visual element inside the RelativeLayout ( Layouts ) markup:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#09C" android:padding="@dimen/widget_margin"> <TextView android:id="@+id/appwidget_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:layout_margin="8dp" android:background="#09C" android:contentDescription="@string/appwidget_text" android:text="@string/appwidget_text" android:textColor="#ffffff" android:textSize="20sp" android:textStyle="bold|italic" /> </RelativeLayout> 

The most interesting is the third file with the source code template for the widget in java. In this form, the template does not bear the functional load, however, it is possible to understand from the comments of the code which method is executed when and for which it answers:

Widget code template
 /** * Implementation of App Widget functionality. */ public class BTCWidget extends AppWidgetProvider { static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { CharSequence widgetText = context.getString(R.string.appwidget_text); // Construct the RemoteViews object RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.btcwidget); views.setTextViewText(R.id.appwidget_text, widgetText); // Instruct the widget manager to update the widget appWidgetManager.updateAppWidget(appWidgetId, views); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // There may be multiple widgets active, so update all of them for (int appWidgetId : appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId); } } @Override public void onEnabled(Context context) { // Enter relevant functionality for when the first widget is created } @Override public void onDisabled(Context context) { // Enter relevant functionality for when the last widget is disabled } } 


It should immediately be said that the Android OS allows you to create a large number of instances of our widget and their behavior will be completely described by the above code. For a deeper understanding of the behavior and life cycle of widgets, I recommend reading the Android Widget article. Below in the comments of the code I will explain only some of the points.

Faced the following complexity: the system allows the widget to be updated (using the updateAppWidget method) no more than once every 30 minutes for reasons of battery saving. But I wanted to be able to update the data at the right time and I found a way around this limitation. For this, the widget has been programmed to force update by clicking on it. Implemented such an action in the following way: by clicking on the widget, an intent (Intent) is sent to the system, which is caught by the widget itself and processed by the start of data update. If someone knows a simpler way - I will be glad to advice in the comments.

Added Functionality Widget Source Code
 /** * Implementation of App Widget functionality. */ public class BTCwidget extends AppWidgetProvider { private static final String SYNC_CLICKED = "btcwidget_update_action"; private static final String WAITING_MESSAGE = "Wait for BTC price"; public static final int httpsDelayMs = 300; //  ,     static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { // RemoteViews        : RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.btcwidget); //   -  TextView views.setTextViewText(R.id.appwidget_text, WAITING_MESSAGE); appWidgetManager.updateAppWidget(appWidgetId, views); String output; //         //      -   HTTPRequestThread thread = new HTTPRequestThread(); thread.start(); try { while (true) { Thread.sleep(300); if(!thread.isAlive()) { output = thread.getInfoString(); break; } } } catch (Exception e) { output = e.toString(); } //    views.setTextViewText(R.id.appwidget_text, output); // Instruct the widget manager to update the widget appWidgetManager.updateAppWidget(appWidgetId, views); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { RemoteViews remoteViews; ComponentName watchWidget; remoteViews = new RemoteViews(context.getPackageName(), R.layout.btcwidget); watchWidget = new ComponentName(context, BTCwidget.class); //         ,    remoteViews.setOnClickPendingIntent(R.id.appwidget_text, getPendingSelfIntent(context, SYNC_CLICKED)); appWidgetManager.updateAppWidget(watchWidget, remoteViews); //    for (int appWidgetId : appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId); } } //   ,         //   @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); if (SYNC_CLICKED.equals(intent.getAction())) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); RemoteViews remoteViews; ComponentName watchWidget; remoteViews = new RemoteViews(context.getPackageName(), R.layout.btcwidget); watchWidget = new ComponentName(context, BTCwidget.class); remoteViews.setTextViewText(R.id.appwidget_text, WAITING_MESSAGE); //updating widget appWidgetManager.updateAppWidget(watchWidget, remoteViews); String output; HTTPRequestThread thread = new HTTPRequestThread(); thread.start(); try { while (true) { Thread.sleep(httpsDelayMs); if(!thread.isAlive()) { output = thread.getInfoString(); break; } } } catch (Exception e) { output = e.toString(); } remoteViews.setTextViewText(R.id.appwidget_text, output); //widget manager to update the widget appWidgetManager.updateAppWidget(watchWidget, remoteViews); } } //  protected PendingIntent getPendingSelfIntent(Context context, String action) { Intent intent = new Intent(context, getClass()); intent.setAction(action); return PendingIntent.getBroadcast(context, 0, intent, 0); } } 


Content of HTTPRequestThread.java class:

Look:
 package com.hakey.btcwidget; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Calendar; class HTTPRequestThread extends Thread{ private static final String urlString = "https://btc-e.nz/api/3/ticker/btc_usd"; String getInfoString() { return output; } private String output = ""; private void requestPrice() { try { URL url = new URL(urlString); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); output = "Price: " + JSONParser.getPrice(response.toString()) + "\n" + getTimeStamp(); } catch (Exception e) { output = e.toString(); } } @Override public void run() { requestPrice(); } private String getTimeStamp() { Calendar calendar = Calendar.getInstance(); if(calendar.get(Calendar.MINUTE)>9) { return "Time: " + calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE); } else { return "Time: " + calendar.get(Calendar.HOUR_OF_DAY) + ":0" + calendar.get(Calendar.MINUTE); } } } 


Parser response from the server - JSONParser.java:

Watch:
 package com.hakey.btcwidget; import org.json.JSONException; import org.json.JSONObject; class JSONParser { static String getPrice(String s) throws JSONException { String price; JSONObject obj = new JSONObject(s); JSONObject pairObj = obj.getJSONObject("btc_usd"); price = pairObj.getString("last"); return price; } } 


Here is the widget described above:

Screenshot
image

The full source code is available here: github.com/hakeydotom/BTCPriceWidget

Source: https://habr.com/ru/post/318482/


All Articles