📜 ⬆️ ⬇️

We write widget HabraKarma ex-CarmaWidget for Android

Yesterday I did update CarmaWidget, a widget that displays your karma on the desktop of a smartphone. Today I will talk about how to write a widget.

image

The principle is:

')


Actually, let's go!

Class for working with the database.



In this class, I will have all the names of columns, tables, the database itself. Methods that create a table, delete, upgrade it if necessary. I also implemented one trick. Due to the fact that the base on Android is SQLite, therefore we are not able to simultaneously write to the same base from several threads. I bypassed it, but at the price of the fact that all calls to the record should now be launched not from the interface stream, otherwise we risk getting Application Not Responding.

The class is called DatabasHelper and inherits SQLiteOpenHelper.

We set all 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";



Traditionally, chew)
USERS_ID - When a user adds a widget to him, an identifier is assigned to him, and if one more such widget is added, then you can get access to some specific one using this identifier. Perhaps now it is still not very clear, just below it will be more detailed.
USERS_ICON and USERS_ICON_URL - there is a CheckBox in the customizer. If it is checked, then we load the icon, if not, then put NULL in the base 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 load it and check with what we just received from the server and if they did not match or USERS_ICON contains NULL, then we actually update icon.

We define the constructor.
	 public DatabaseHelper (Context context) {
		 super (context, DATABASE_NAME, null, DATABASE_VERSION);
	 }

Since the base is one, I simplified it a little.

We write a method for generating a 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 changing the version of the table.
	 @Override
	 public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) {
		 db.execSQL ("DROP TABLE IF EXISTS" + TABLE_NAME);
		 onCreate (db);
	 }

For example, in the development process, we needed to add a column. Rule the query generator and increase the version. The base will be created anew. But this is the easiest way, in a good way, you must add it without deleting it.

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;

Immediately make a reservation, perhaps the decision is not quite correct, but it seems to work. If there is no possibility to get access to the record, then wait half a second and try again.

We write class for work with the Habra server.



Set constants
	 private static String USER_URL = "http: // <username> .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 is a link to the icon "without avatar" Habra.

Method for obtaining 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 ("<habrauser>"))
				 {
					 if (answerInputString.contains ("<error> 404 </ error>"))
					 {
						 result [2] = RESULT_USER_NOT_FOUND;
					 }
					 else
					 {
						 result [0] = answerInputString.substring (answerInputString.indexOf ("<karma>") + "<karma>". length (), answerInputString.indexOf ("</ karma>"));
						 result [1] = answerInputString.substring (answerInputString.indexOf ("<rating>") + "<rating>". length (), answerInputString.indexOf ("</ rating>"));
						 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 api Habra can return karma and rating, for example 12, then you need to bring it to the form of 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. At api there is no such thing, you have to parse the user profile.
	 public String getUserpicUrl (String username)
	 {
		 String result = "";
		 result = RESULT_NO_USERPIC;
		 String url = USER_URL.replace ("<username>", 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 ("<h1 class = \" habrauserava \ ">"));
				 answer = answer.substring (answer.indexOf ("<img src = \" ") +" <img src = \ "". length (), answer.indexOf ("\" alt ")));
				 result = answer;
			 }
		 } catch (Exception e)
		 {
			
		 }
		 return result;
	 }


And actually download 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;
	 }


Widget adjuster


image

The Config class is an ordinary Activity, but it must return the result of its work to the widget. That is, if we clicked to add a widget, this customizer will appear, if it correctly performed its work, then we set the result as positive and close it, if it, for example, found a user, then we notify about it and then the user either tries again or closes . So closing without successful data entry should set the result as canceled. Slightly lower in practice.

 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 the handler for clicking the Done button. setResult (RESULT_CANCELLED) - just initially sets the result of the action as canceled. If everything went well, we will change it.

We add here Runnable updater - 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 entered data from the interface elements. And we load karma for% username%. Then from the intent we get the id of the widget. Create a class variable ContentValues, in order to put the result in the database. Then, using RemoteViews, we update the view of the widget for which we are doing the configuration and put this kindly into the base. At the very end, we create an Intent updaterIntent, with which we will do 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 was and would use it again. setData is done only to make it unique.

Using the AlarmManager, I set the time for the next update. In general, devGuide tells us to use update time through the description of the widget provider, more on that below. But it 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 off the screen. AlarmManager in this regard is very convenient, it does not cause updates while the device is in suspension, but if at least one update had to happen during this time, then it will call it once after launch.

At the end of this runnable, I call Handler, which will process the results and pass it the third line from the getStats (username) method, which contains an 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 a corresponding message, but if the result is RESULT_OK, specified in the class with api Habra, then we only close this Activity.

AppWidgetProvider


The main class where widgets are updated.

Set the 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. We need it in order to convert the text values ​​of the spinner to milliseconds for the 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 the 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, nu, nu, nu
		 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);
	 }

In general, it is called once at the start of this provider. Here are all the timers for existing elements. For example, we just installed the widget. The method will be called, but since the base is empty, nothing will be started. But when the phone is restarted, it will call this method, and if there were elements there, then it will start the timers for updates and set them to the past values ​​from the database.

OnDeleted () method
	 @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 () method
	 public 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_ICLE_RAF_US, _Efe, USHR.USERS_ICEL_ICLE_CARMA, DatabaseHelper.USERS_KARMA, DatabaseHelper.TABLE_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;
	 }

In it, the update takes place, it is quite similar to the update class Config

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 type:
 <? xml version = "1.0" encoding = "utf-8"?>
 <appwidget-provider xmlns: android = "http://schemas.android.com/apk/res/android"
	 android: initialLayout = "@ layout / widget"
	 android: minHeight = "72dp"
	 android: minWidth = "146dp"
	 android: configure = "com.nixan.carmawidget.Config" />

Here you can add the update time, but, once again, then it will be the same for all widgets.

And the final rule of AndroidManifest.xml
 <? xml version = "1.0" encoding = "utf-8"?>
 <manifest xmlns: android = "http://schemas.android.com/apk/res/android"
       package = "com.nixan.carmawidget"
       android: versionCode = "3"
       android: versionName = "1.2">
     <application android: icon = "@ drawable / icon" android: label = "@ string / app_name" android: theme = "@ android: style / Theme.Light">
         <activity android: name = ". Config" android: screenOrientation = "portrait">
             <intent-filter>
    			 <action android: name = "android.appwidget.action.APPWIDGET_CONFIGURE" />
    		 </ intent-filter>
         </ activity>
		 <receiver android: name = ". Widget"
		     android: label = "@ string / app_name">
		    	 <intent-filter>
		    		 <action android: name = "android.appwidget.action.APPWIDGET_UPDATE" />
		    	 </ intent-filter>	
		    	 <meta-data android: name = "android.appwidget.provider" android: resource = "@ xml / carmawidget_provider" />
		 </ receiver>
		 <receiver android: name = ". Widget"
		     android: label = "@ string / app_name">
		    	 <intent-filter>
		    		 <action android: name = "android.appwidget.action.APPWIDGET_UPDATE" />
		    		 <data android: scheme = "karma_widget" />
		    	 </ intent-filter>	
		    	 <meta-data android: name = "android.appwidget.provider" android: resource = "@ xml / carmawidget_provider" />
		 </ receiver>
     </ application>
     <uses-sdk android: minSdkVersion = "3" />
	 <uses-permission android: name = "android.permission.INTERNET" />
 </ manifest>


Interface design lies in the source, if something is not clear, then you can read here and here

Download source (172 Kb)
Download the application (51 Kb)
Or in the market pub: nixan

Thanks for attention)

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


All Articles