![](http://habrastorage.org/getpro/habr/avatars/cb9/baa/74f/cb9baa74f6a9b905b16921587deddff9.jpg)
We write the HabraKarma ex-CarmaWidget widget for Android
Yesterday, I updated CarmaWidget, a widget that displays your karma on the smartphone’s desktop. Today I’ll talk about how to write a widget.
![image](http://nixan.org/habr/habracarma/device1.png)
The principle is as follows:
Actually, let's go!
In this class, I will have all the names of the columns, tables, the base itself. Methods that create the table, delete, upgrade it if necessary. I also have one receiver implemented. Due to the fact that the base on Android is SQLite, therefore we are not able to write to the same database from multiple threads at the same time. I got around this, but at the cost of all recording calls now starting from outside the interface thread, otherwise we risk getting Application Not Responding.
The class is called DatabasHelper and inherits SQLiteOpenHelper.
We set all the constants:
Traditionally, I chew)
USERS_ID - When a user adds a widget, an identifier is assigned to him and if another widget of the same type is added, then it will be possible to get access to a specific one using this identifier. Perhaps it’s still not very clear, it will be more detailed a little lower.
USERS_ICON and USERS_ICON_URL - there is a CheckBox in the customizer. If it is checked, then we load the icon; if not, we put NULL in the database for both columns. Then, when updating, we read from the USERS_ICON_URL database and if it is not NULL, then the user indicated in the settings that we want to download it and check that we just received from the server and if they did not match or USERS_ICON contains NULL, then we actually updating the icon.
Define the constructor.
Since there is only one base, I simplified it a little.
We are writing a method for generating an SQL query to create a table.
The method that creates the base.
The onUpgrade method, which is called when the table version is changed.
For example, during the development process, we needed to add some column. We correct the request generator and increase the version. The base will be created anew. But this is the easiest way, in a good way you need to add it without deleting.
A method that returns the base to write.
I’ll make a reservation right away, maybe the solution is not quite right, but it seems to work. If there is no way to access the recording, then we wait half a second and try again.
We set constants
RESULT_NO_USERPIC - this is a link to the "without avatar" icon of the hub.
Method for getting karma and rating.
result - the output value of the method, an array of three lines, where the first is karma, the second is rating and the third is an error message.
Since the api Habra can return karma and rating, for example 12, then you need to bring it to the form 12.00.
Get link icons. The api does not have this;
![image](http://nixan.org/habr/habracarma/device1.png)
The principle is as follows:
- Class - settings for the widget that launches when the latter is added to the desktop.
- The class is the information provider for the widget in which all timers live.
- Database - for storing user information.
- Two layouts for the widget and customizer.
Actually, let's go!
Class for working with the database.
In this class, I will have all the names of the columns, tables, the base itself. Methods that create the table, delete, upgrade it if necessary. I also have one receiver implemented. Due to the fact that the base on Android is SQLite, therefore we are not able to write to the same database from multiple threads at the same time. I got around this, but at the cost of all recording calls now starting from outside the interface thread, otherwise we risk getting Application Not Responding.
The class is called DatabasHelper and inherits SQLiteOpenHelper.
We set all the constants:
public static String DATABASE_NAME = "carmawidget.db" private static int DATABASE_VERSION = 1; public static String TABLE_NAME = "users"; public static String USERS_ID = "_id"; public static String USERS_NAME = "name"; public static String USERS_KARMA = "karma"; public static String USERS_RATE = "rate"; public static String USERS_UPDATE = "_update"; public static String USERS_ICON = "icon"; public static String USERS_ICON_URL = "icon_url";
- DATABASE_NAME - database name
- DATABASE_VERSION - database version. When this value is changed, the onUpdate () method is called, about which a little later
- TABLE_NAME - table name
- USERS_ID - the first column of the table. It contains a unique identifier for the user, which in essence is also the identifier of the widget.
- USERS_KARMA - Karma.
- USERS_RATE - rating.
- USERS_UPDATE - update rate.
- USERS_ICON_URL - link to the user icon. If it changes when updating, then we update the icon itself.
- USERS_ICON - icon.
Traditionally, I chew)
USERS_ID - When a user adds a widget, an identifier is assigned to him and if another widget of the same type is added, then it will be possible to get access to a specific one using this identifier. Perhaps it’s still not very clear, it will be more detailed a little lower.
USERS_ICON and USERS_ICON_URL - there is a CheckBox in the customizer. If it is checked, then we load the icon; if not, we put NULL in the database for both columns. Then, when updating, we read from the USERS_ICON_URL database and if it is not NULL, then the user indicated in the settings that we want to download it and check that we just received from the server and if they did not match or USERS_ICON contains NULL, then we actually updating the icon.
Define the constructor.
public DatabaseHelper (Context context) { super (context, DATABASE_NAME, null, DATABASE_VERSION); }
Since there is only one base, I simplified it a little.
We are writing a method for generating an SQL query to create a table.
private String usersCreate () { StringBuilder result = new StringBuilder ("CREATE TABLE"); result.append (TABLE_NAME); result.append ("("); result.append (USERS_ID); result.append ("INTEGER PRIMARY KEY,"); result.append (USERS_NAME); result.append ("TEXT NOT NULL,"); result.append (USERS_KARMA); result.append ("TEXT DEFAULT '0,00',"); result.append (USERS_RATE); result.append ("TEXT DEFAULT '0,00',"); result.append (USERS_ICON_URL); result.append ("TEXT,"); result.append (USERS_UPDATE); result.append ("INTEGER DEFAULT '0',"); result.append (USERS_ICON); result.append ("BLOB);"); return result.toString (); }
The method that creates the base.
@Override public void onCreate (SQLiteDatabase db) { db.execSQL (usersCreate ()); }
The onUpgrade method, which is called when the table version is changed.
@Override public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL ("DROP TABLE IF EXISTS" + TABLE_NAME); onCreate (db); }
For example, during the development process, we needed to add some column. We correct the request generator and increase the version. The base will be created anew. But this is the easiest way, in a good way you need to add it without deleting.
A method that returns the base to write.
@Override public SQLiteDatabase getWritableDatabase () { SQLiteDatabase db = null; while (db == null) { try { db = super.getWritableDatabase (); } catch (SQLiteException e) { try { Thread.sleep (500); } catch (InterruptedException e1) { } } } return db;
I’ll make a reservation right away, maybe the solution is not quite right, but it seems to work. If there is no way to access the recording, then we wait half a second and try again.
We write a class for working with the Habra server.
We set constants
private static String USER_URL = "http: //.habrahabr.ru / "; private static String API_URL = "http://habrahabr.ru/api/profile/"; public static String RESULT_OK = "ok"; public static String RESULT_SERVER = "server"; public static String RESULT_USER_NOT_FOUND = "not_found"; public static String RESULT_NETWORK = "network"; public static String RESULT_NO_USERPIC = "http://habrahabr.ru/i/avatars/stub-user-middle.gif";
RESULT_NO_USERPIC - this is a link to the "without avatar" icon of the hub.
Method for getting karma and rating.
public String [] getStats (String username) { String [] result = new String [] {"0.00", "0.00", RESULT_OK}; String url = API_URL + Uri.encode (username); String proxyHost = android.net.Proxy.getDefaultHost (); int proxyPort = android.net.Proxy.getDefaultPort (); HttpClient httpClient = new DefaultHttpClient (); if (proxyPort> 0) { HttpHost proxy = new HttpHost (proxyHost, proxyPort); httpClient.getParams (). setParameter (ConnRoutePNames.DEFAULT_PROXY, proxy); } HttpGet httpGet = new HttpGet (url); try { HttpResponse response = httpClient.execute (httpGet); if (response.getStatusLine (). getStatusCode () == HttpStatus.SC_OK) { BufferedReader reader = new BufferedReader (new InputStreamReader (response.getEntity (). GetContent ())); StringBuilder sb = new StringBuilder (); String line = null; while ((line = reader.readLine ())! = null) { sb.append (line + System.getProperty ("line.separator")); } String answerInputString = sb.toString (); if (answerInputString.contains ("")) { if (answerInputString.contains (" 404 ")) { result [2] = RESULT_USER_NOT_FOUND; } else { result [0] = answerInputString.substring (answerInputString.indexOf ("") +" ".length (), answerInputString.indexOf (" ")); result [1] = answerInputString.substring (answerInputString.indexOf ("") +" ".length (), answerInputString.indexOf (" ")); result [0] = formatter (result [0]); result [1] = formatter (result [1]); } } else { result [2] = RESULT_SERVER; } } else { result [2] = RESULT_SERVER; } } catch (Exception e) { result [2] = RESULT_NETWORK; } return result; }
result - the output value of the method, an array of three lines, where the first is karma, the second is rating and the third is an error message.
Since the api Habra can return karma and rating, for example 12, then you need to bring it to the form 12.00.
private String formatter (String string) { string = string.replace (".", ","); if (! string.contains (",")) { string = string + ", 00"; } for (int i = 0; i <2 - string.split (",") [1] .length (); i ++) { string = string + "0"; } return string; }
Get link icons. The api does not have this;
public String getUserpicUrl (String username) { String result = ""; result = RESULT_NO_USERPIC; String url = USER_URL.replace ("", username); String proxyHost = android.net.Proxy.getDefaultHost (); int proxyPort = android.net.Proxy.getDefaultPort (); HttpClient httpClient = new DefaultHttpClient (); if (proxyPort> 0) { HttpHost proxy = new HttpHost (proxyHost, proxyPort); httpClient.getParams (). setParameter (ConnRoutePNames.DEFAULT_PROXY, proxy); } HttpGet httpGet = new HttpGet (url); try { HttpResponse response = httpClient.execute (httpGet); if (response.getStatusLine (). getStatusCode () == HttpStatus.SC_OK) { BufferedReader reader = new BufferedReader (new InputStreamReader (response.getEntity (). GetContent ())); StringBuilder sb = new StringBuilder (); String line = null; while ((line = reader.readLine ())! = null) { sb.append (line + System.getProperty ("line.separator")); } String answer = sb.toString (); result = RESULT_NO_USERPIC; answer = answer.substring (answer.indexOf (" ")); answer = answer.substring (answer.indexOf ("
And actually load the icon.public Bitmap imageDownloader (String url) { Bitmap result = null; String proxyHost = android.net.Proxy.getDefaultHost (); int proxyPort = android.net.Proxy.getDefaultPort (); try { URL bitmapUrl = new URL (url); HttpURLConnection connection; if (proxyPort> 0) { InetSocketAddress proxyAddr = new InetSocketAddress (proxyHost, proxyPort); Proxy proxy = new Proxy (Proxy.Type.HTTP, proxyAddr); connection = (HttpURLConnection) bitmapUrl.openConnection (proxy); } else { connection = (HttpURLConnection) bitmapUrl.openConnection(); } connection.setDoInput(true); connection.connect(); InputStream inputStream = connection.getInputStream(); result = BitmapFactory.decodeStream(inputStream); } catch(Exception e) { } return result; }Настройщик виджета
Класс Config — обыкновенный Activity, но он должен возвращать результат своей работы виджету. То есть если мы ткнули добавить виджет, появится этот настройщик, если он корректно выполнил свою работу то мы выставляем результат как положительный и закрываем его, если он, к примеру на нашел пользователя, то мы об этом уведомляем и дальше пользователь либо пробует заново либо закрывает. Так вот закрытие без успешного ввода данных должно выставлять результат как отмененный. Чуть ниже на деле.public class Config extends Activity { / ** Called when the activity is first created. * / Context context; Thread updaterThread = new Thread (); ProgressDialog progressDialog; @Override public void onCreate (Bundle savedInstanceState) { super.onCreate (savedInstanceState); setContentView (R.layout.main); context = this; setResult (RESULT_CANCELED); progressDialog = new ProgressDialog (this); progressDialog.setMessage (getApplicationContext (). getResources (). getString (R.string.loading)); Button ready = (Button) findViewById (R.id.submit); ready.setOnClickListener (new OnClickListener () { @Override public void onClick (View v) { // TODO Auto-generated method stub if (updaterThread.getState () == State.NEW || updaterThread.getState () == State.TERMINATED) { updaterThread = new Thread (updater); updaterThread.start (); progressDialog.show (); } } }); } }
We expose the interface and write a handler for clicking the Finish button. setResult (RESULT_CANCELLED) - just initially sets the result of the action as canceled. If everything went well, then we will change it.
Add Runnable updater here - a separate stream in which the initial data loading will take place.Runnable updater = new Runnable () { @Override public void run () { // TODO Auto-generated method stub String username = ((EditText) findViewById (R.id.username)). GetText (). ToString (); int update = ((Spinner) findViewById (R.id.update_value)). getSelectedItemPosition (); boolean picupdate = ((CheckBox) findViewById (R.id.load_pic)). isChecked (); String [] values = new HabrahabrAPI (). GetStats (username); if (values[2].equals(HabrahabrAPI.RESULT_OK)) { Intent intent = getIntent(); if (intent.getExtras() != null) { int appWidgetId = intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { ContentValues contentValues = new ContentValues(); contentValues.put(DatabaseHelper.USERS_ID, appWidgetId); contentValues.put(DatabaseHelper.USERS_NAME, username); contentValues.put(DatabaseHelper.USERS_KARMA, values[0]); contentValues.put(DatabaseHelper.USERS_RATE, values[1]); contentValues.put(DatabaseHelper.USERS_UPDATE, update); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getApplicationContext()); RemoteViews views = new RemoteViews (getApplicationContext (). GetPackageName (), R.layout.widget); if (picupdate == true) { String icon = new HabrahabrAPI (). GetUserpicUrl (username); contentValues.put (DatabaseHelper.USERS_ICON_URL, icon); if (! icon.equals (HabrahabrAPI.RESULT_NO_USERPIC)) { Bitmap userpic = new HabrahabrAPI (). ImageDownloader (icon); if (userpic! = null) { ByteArrayOutputStream baos = new ByteArrayOutputStream (); userpic.compress (Bitmap.CompressFormat.PNG, 100, baos); contentValues.put (DatabaseHelper.USERS_ICON, baos.toByteArray ()); views.setBitmap (R.id.icon, "setImageBitmap", userpic); } } else { views.setBitmap(R.id.icon, "setImageBitmap", BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.userpic)); ByteArrayOutputStream baos = new ByteArrayOutputStream(); BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.userpic).compress(Bitmap.CompressFormat.PNG, 100, baos); contentValues.put(DatabaseHelper.USERS_ICON, baos.toByteArray()); } } else { contentValues.putNull(DatabaseHelper.USERS_ICON); contentValues.putNull(DatabaseHelper.USERS_ICON_URL); } SQLiteDatabase db = new DatabaseHelper(getApplicationContext()).getWritableDatabase(); db.insert(DatabaseHelper.TABLE_NAME, null, contentValues); db.close(); contentValues.clear(); views.setTextViewText(R.id.karma, values[0]); views.setTextViewText(R.id.rating, values[1]); appWidgetManager.updateAppWidget(appWidgetId, views); Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); setResult(RESULT_OK, resultValue); Intent updaterIntent = new Intent(); updaterIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updaterIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {appWidgetId}); updaterIntent.setData(Uri.withAppendedPath(Uri.parse(Widget.URI_SCHEME + "://widget/id/"), String.valueOf(appWidgetId))); PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, updaterIntent, PendingIntent.FLAG_UPDATE_CURRENT); AlarmManager alarmManager = (AlarmManager) getApplicationContext (). GetSystemService (Context.ALARM_SERVICE); alarmManager.setRepeating (AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime () + (Widget.updatePeriodsMinutes [update] * 60000), Widget.updatePeriodsMinutes [update] * 60000, pendingIntent); } } } Bundle data = new Bundle (); data.putString ("RESULT", values [2]); Message message = new Message (); message.setData (data); handler.sendMessage (message); } };
First we get the input from the interface elements. And load the karma for% username%. Then from intent we get the widget identifier. We create a variable of the ContentValues class in order to put the result into the database. Then, using RemoteViews, we update the widget view, for which we do the configuration and put this stuff in the database. At the very end, we create an Intent updaterIntent, with which we will make updates. Android works in such a way that if we did not add setData (Uri) data to this intent, then it would consider that such an intent already existed and would use it again. setData is only made to be unique.
Using AlarmManager, I set the time for the next update. In general, devGuide tells us to use the update time through the description of the widget provider, more on that below. But this will not allow us to configure different update times. You can use TimerTask, but when you turn on the screen, it will immediately launch updates while the device was turned off. AlarmManager in this regard is very convenient, it does not cause updates while the device is in suspension, but if at least one update was to happen during this time, it will call it once after launch.
At the end of this runnable, I call the Handler, in which the results will be processed and pass it the third line from the getStats (username) method, which contains the error message.
Result handler.Handler handler = new Handler () { @Override public void handleMessage (Message message) { progressDialog.dismiss (); AlertDialog.Builder builder = new AlertDialog.Builder (context); builder.setIcon (android.R.drawable.ic_dialog_alert); builder.setPositiveButton (R.string.ok, new DialogInterface.OnClickListener () { @Override public void onClick (DialogInterface dialog, int which) { // TODO Auto-generated method stub dialog.dismiss (); } }); String result = message.getData (). GetString ("RESULT"); if (result.equals (HabrahabrAPI.RESULT_NETWORK)) { builder.setTitle (R.string.error_network); builder.setMessage (R.string.try_later); } if (result.equals (HabrahabrAPI.RESULT_USER_NOT_FOUND)) { builder.setTitle (R.string.error_user); builder.setMessage (R.string.try_user); } if (result.equals (HabrahabrAPI.RESULT_SERVER)) { builder.setTitle (R.string.error_server); builder.setMessage (R.string.try_later); } if (result.equals (HabrahabrAPI.RESULT_OK)) { finish (); } else { builder.show (); } } };
Depending on this result, we issue the corresponding message, but if the result is RESULT_OK, set in the class with the api habr, then we only close this Activity.AppWidgetProvider
The main class where widgets are updated.
We set constants:public static int [] updatePeriodsMinutes = new int [] {30, 60, 180, 360, 720}; public static String URI_SCHEME = "karma_widget";
The array in this case contains the minutes for the SpinnerView in the Config class. It is needed in order to translate the text values of the spinner into milliseconds for AlarmManager. In Config, we take the index of the selected item and put it in the database. In the provider, we take an element from an array with an index from the base and multiply it by 60,000. We get milliseconds.
OnEnabled () method@Override public void onEnabled (Context context) { SQLiteDatabase db = new DatabaseHelper (context) .getReadableDatabase (); Cursor cursor = db.query (DatabaseHelper.TABLE_NAME, new String [] {DatabaseHelper.USERS_ID, DatabaseHelper.USERS_KARMA, DatabaseHelper.USERS_RATE, DatabaseHelper.USERS_UPDATE, DatabaseHelper.USERS_ICON}, null, null, null, null, null, null, null, null, null, null, null, null while (cursor.moveToNext ()) { RemoteViews views = new RemoteViews (context.getPackageName (), R.layout.widget); int id = cursor.getInt (cursor.getColumnIndex (DatabaseHelper.USERS_ID)); String karma = cursor.getString (cursor.getColumnIndex (DatabaseHelper.USERS_KARMA)); String rate = cursor.getString (cursor.getColumnIndex (DatabaseHelper.USERS_RATE)); byte [] icon = cursor.getBlob (cursor.getColumnIndex (DatabaseHelper.USERS_ICON)); int update = cursor.getInt (cursor.getColumnIndex (DatabaseHelper.USERS_UPDATE)); views.setTextViewText (R.id.karma, karma); views.setTextViewText (R.id.rating, rate); if (icon == null) { views.setBitmap (R.id.icon, "setImageBitmap", BitmapFactory.decodeResource (context.getResources (), R.drawable.userpic)); } else { views.setBitmap(R.id.icon, "setImageBitmap", BitmapFactory.decodeByteArray(icon, 0, icon.length)); } AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); appWidgetManager.updateAppWidget(id, views); Intent updaterIntent = new Intent(); updaterIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updaterIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {id}); updaterIntent.setData(Uri.withAppendedPath(Uri.parse(URI_SCHEME + "://widget/id/"), String.valueOf(id))); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, updaterIntent, PendingIntent.FLAG_UPDATE_CURRENT); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), updatePeriodsMinutes[update] * 60000, pendingIntent); } cursor.close(); db.close(); super.onEnabled(context); }
Вообще вызывается он один раз при старте этого провайдера. Тут здаются все таймеры для уже существующих элементов. К примеру, мы только установили виджет. Метод вызовется, но так как база пустая, ничего не будет запущено. Но при рестарте телефона, он вызовет этот метод, и если там были элементы, то он запустит таймеры для обновлений и выставит им прошлые значения из базы данных.
Метод onDeleted()@Override public void onDeleted(Context ctxt, int[] ids) { final int[] appWidgetIds = ids; final Context context = ctxt; new Thread(new Runnable(){ @Override public void run() { for (int i = 0; i <appWidgetIds.length; i ++) { Intent intent = new Intent (); intent.setAction (AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra (AppWidgetManager.EXTRA_APPWIDGET_IDS, new int [] {appWidgetIds [i]}); intent.setData (Uri.withAppendedPath (Uri.parse (URI_SCHEME + ": // widget / id /"), String.valueOf (appWidgetIds [i]))); PendingIntent pendingIntent = PendingIntent.getBroadcast (context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); AlarmManager alarmManager = (AlarmManager) context.getSystemService (Context.ALARM_SERVICE); alarmManager.cancel (pendingIntent); SQLiteDatabase db = new DatabaseHelper (context) .getWritableDatabase (); db.delete (DatabaseHelper.TABLE_NAME, DatabaseHelper.USERS_ID + "=" + appWidgetIds [i], null); db.close (); } } }). start (); super.onDeleted (ctxt, ids); }
Called when we remove the widget from the desktop. Well, here we just stop the timer and delete it from the database.
OnUpdate () method@Override public void onUpdate (Context ctxt, AppWidgetManager mgr, int [] appWidgetIds) { final Context context = ctxt; final AppWidgetManager appWidgetManager = mgr; final int [] ids = appWidgetIds; new Thread (new Runnable () { @Override public void run () { // TODO Auto-generated method stub for (int i = 0; i <ids.length; i ++) { appWidgetManager.updateAppWidget (ids [i], buildUpdate (context, ids [i])); } } }). start (); super.onUpdate (ctxt, mgr, appWidgetIds); }
AlarmManager will call this method when it needs to be updated.
BuildUpdate () methodpublic RemoteViews buildUpdate (Context context, int id) { RemoteViews views = new RemoteViews (context.getPackageName (), R.layout.widget); SQLiteDatabase db = new DatabaseHelper (context) .getReadableDatabase (); Cursor cursor = db.query (DatabaseHelper.TABLE_NAME, new String [] {DatabaseHelper.USERS_ID, DatabaseHelper.USERS_KARMA, DatabaseHelper.USERS_RATE, DatabaseHelper.USERS_ICON, DatabaseHelper.USERS_ICON_URL, DatabaseHelper.USERS_NAME_ + +}} .valueOf (id), null, null, null, null); if (cursor.getCount ()! = 0) { ContentValues contentValues = new ContentValues (); cursor.moveToFirst (); String username = cursor.getString (cursor.getColumnIndex (DatabaseHelper.USERS_NAME)); String karma = cursor.getString (cursor.getColumnIndex (DatabaseHelper.USERS_KARMA)); String rate = cursor.getString (cursor.getColumnIndex (DatabaseHelper.USERS_RATE)); String icon_url = cursor.getString (cursor.getColumnIndex (DatabaseHelper.USERS_ICON_URL)); byte [] icon = cursor.getBlob (cursor.getColumnIndex (DatabaseHelper.USERS_ICON)); String [] updated = new HabrahabrAPI (). GetStats (username); if (updated [2] .equals (HabrahabrAPI.RESULT_OK)) { if (! updated [0] .equals (karma) ||! updated [1] .equals (rate)) { karma = updated [0]; rate = updated [1]; contentValues.put (DatabaseHelper.USERS_KARMA, karma); contentValues.put (DatabaseHelper.USERS_RATE, rate); } } views.setTextViewText (R.id.karma, karma); views.setTextViewText (R.id.rating, rate); if (icon_url! = null) { String updatedIconUrl = new HabrahabrAPI (). GetUserpicUrl (username); if ((icon == null ||! icon_url.equals (updatedIconUrl)) &&! updatedIconUrl.equals (HabrahabrAPI.RESULT_NO_USERPIC)) { icon_url = updatedIconUrl; Log.d ("CarmaWidget", "Downloaded new userpic"); Bitmap iconBitmap = new HabrahabrAPI (). ImageDownloader (icon_url); if (iconBitmap! = null) { ByteArrayOutputStream baos = new ByteArrayOutputStream (); iconBitmap.compress (CompressFormat.PNG, 100, baos); icon = baos.toByteArray (); contentValues.put (DatabaseHelper.USERS_ICON_URL, icon_url); contentValues.put (DatabaseHelper.USERS_ICON, icon); views.setBitmap (R.id.icon, "setImageBitmap", iconBitmap); } } } else { if (icon == null) { views.setBitmap (R.id.icon, "setImageBitmap", BitmapFactory.decodeResource (context.getResources (), R.drawable.userpic)); } else { views.setBitmap (R.id.icon, "setImageBitmap", BitmapFactory.decodeByteArray (icon, 0, icon.length)); } } cursor.close (); db.close (); if (contentValues.size ()! = 0) { db = new DatabaseHelper (context) .getWritableDatabase (); db.update (DatabaseHelper.TABLE_NAME, contentValues, DatabaseHelper.USERS_ID + "=" + String.valueOf (id), null); db.close (); } } else { cursor.close (); db.close (); } return views; }
The update takes place in it, it is quite similar to the Config class update.
Then we need to set the customizer, width, height and layout of our widget. To do this, in the res / xml folder, you need to create an xml file of the following form:
You can add the update time to this, but, I repeat again, then it will be the same for all widgets.
And the final - edit AndroidManifest.xml
The design of the interface lies in the source, if something is not clear, then you can read it here and here
Download the source (172 Kb)
Download the application (51 Kb)
Or in the pub market: nixan
Thank you for your attention)