SQLite BLOB column to SimpleCursorAdapter with ViewBinder - android

I'm trying to display a list of contacts that are currently stored in a SQLiteDatabase.
Previously I've retrieved ContactsContract.Contacts.PHOTO_THUMBNAIL_URI and stored it in a form of byte[] in the BLOB column of my database rows.
Now when I'm trying to extract the thumbnails back, by decoding them to Bitmaps in MyBinderView class, the pictures don't appear, instead I see empty spaces(the default image, ic_launcher, is showed correctly). My ListView row layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="1dp">
<ImageView
android:id="#+id/thumbnail"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dp"
android:layout_weight="2"
android:layout_height="50dp"
android:layout_marginRight="1dp"
android:src="#drawable/ic_launcher"/>
<TextView
android:id="#+id/email"
android:layout_gravity="center_horizontal|center_vertical"
android:layout_width="0dp"
android:layout_weight="5"
android:layout_height="wrap_content"/>
</LinearLayout>
ListFragment class:
//DataBaseHelper.PHOTO contains a BLOB fetched from sqlite database
//DataBaseHelper.NAME is a String (no problem here)
String[] from = { DataBaseHelper.PHOTO, DataBaseHelper.NAME };
int[] to = new int[] { R.id.thumbnail, R.id.email };
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText("No E-mail buddies found");
// We have a menu item to show in action bar.
// setHasOptionsMenu(true);
contacts = new DataBaseHelper(getActivity());
contacts.open();
// Create an empty adapter we will use to display the loaded data.
mAdapter = new SimpleCursorAdapter(getActivity(), R.layout.contacts,
null, from, to);
mAdapter.setViewBinder(new MyViewBinder());
setListAdapter(mAdapter);
// Start out with a progress indicator.
setListShown(false);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
ViewBinder class for the photo to be inserted correctly:
public class MyViewBinder implements ViewBinder{
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
// TODO Auto-generated method stub
int viewId = view.getId();
Log.i("ViewBinder: view", Integer.toString(viewId));
Log.i("ViewBinder: name",cursor.getString(2));
Log.i("ViewBinder: email",cursor.getString(3));
Log.i("ViewBinder: photo",cursor.getBlob(4)==null?"NO Photo":"Has photo");
switch(viewId){
case R.id.thumbnail:
ImageView picture = (ImageView) view;
byte[] blob = cursor.getBlob(columnIndex);
if(blob!=null){
picture.setImageBitmap(
BitmapFactory.decodeByteArray(blob, 0, blob.length)
);
}
else
picture.setImageResource(R.drawable.ic_launcher);
return true;
}
return false;
}
}
Any help would be appreciated.

ContactsContract.Contacts.PHOTO_THUMBNAIL_URI
Provides a path to the thumbnail that can be retrieved by.
So after understanding that you build a URI using this path by calling parse function.
next you query your new uri with the help of this embedded class -
private static class PhotoQuery {
public static final String[] PROJECTION = {
Photo.PHOTO
};
public static final int PHOTO = 0;
}
using the code bellow you'll extract the needed byte[] that solved the issue.
the point of getting byte[] is to be able to store it in your DB and manipulate it later on when needed.
private byte[] getImage(String uriString){
if(uriString==null)
return null;
Uri myuri = Uri.parse(uriString);
Cursor photoCursor = getContentResolver().query(myuri, PhotoQuery.PROJECTION, null, null, null);
if (photoCursor != null) {
try {
if (photoCursor.moveToFirst()) {
final byte[] photoBytes = photoCursor.getBlob(PhotoQuery.PHOTO);
if (photoBytes != null) {
return photoBytes;
}
}
} finally {
photoCursor.close();
}
}
return null;
}
Hope it'll help someone
cheers :)

Related

Handle Blob data type through ContentValues

I am trying to store and retrieve image data in Sqlite Db.
To do so I firstly stored in local device memory an example pic (path: storage/emulated/0/Download/).
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private final String SAMPLE_IMAGE_PATH = "/storage/emulated/0/Download/image.jpg";
Then I set up an insert method to feed the db with these example data:
private void insertProduct() {
// Create a ContentValues object where column names are the keys,
// and sample attributes are the values.
ContentValues values = new ContentValues();
values.put(InventoryContract.ProductEntry.COLUMN_PRODUCT_NAME, sampleName);
values.put(InventoryContract.ProductEntry.COLUMN_PRODUCT_QTY, sampleQty);
values.put(InventoryContract.ProductEntry.COLUMN_PRODUCT_PRICE, SamplePrice);
values.put(InventoryContract.ProductEntry.COLUMN_EMAIL, sampleMail);
values.put(InventoryContract.ProductEntry.COLUMN_PHONE, samplePhone);
values.put(InventoryContract.ProductEntry.COLUMN_PRODUCT_PIC, SAMPLE_IMAGE_PATH);
//insert a new row
Uri newUri = getContentResolver().insert(InventoryContract.ProductEntry.CONTENT_URI,values);
}
and I define the onCreateLoader method as follows:
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// Define a projection that specifies the columns from the table we care about.
String[] projection = {
InventoryContract.ProductEntry._ID,
InventoryContract.ProductEntry.COLUMN_PRODUCT_PIC,
InventoryContract.ProductEntry.COLUMN_PRODUCT_PRICE,
InventoryContract.ProductEntry.COLUMN_PRODUCT_QTY,
InventoryContract.ProductEntry.COLUMN_PRODUCT_NAME};
// This loader will execute the ContentProvider's query method on a background thread
return new CursorLoader(this,
InventoryContract.ProductEntry.CONTENT_URI,
projection,
null,
null,
null);
}
In the CursorAdapter class I updated the listView adding the data from db in bindView() method:
public void bindView(View view, Context context, Cursor cursor) {
// Find individual views that we want to modify in the list item layout
TextView nameTextView = (TextView) view.findViewById(R.id.prod_name);
TextView priceTextView = (TextView) view.findViewById(R.id.prod_price);
TextView qtyTextView = (TextView) view.findViewById(R.id.prod_qty);
ImageView prodImageView = (ImageView) view.findViewById(R.id.prod_img);
// Find the columns of attributes that we're interested in
int nameColumnIndex = cursor.getColumnIndex(InventoryContract.ProductEntry.COLUMN_PRODUCT_NAME);
int priceColumnIndex = cursor.getColumnIndex(InventoryContract.ProductEntry.COLUMN_PRODUCT_PRICE);
int qtyColumnIndex = cursor.getColumnIndex(InventoryContract.ProductEntry.COLUMN_PRODUCT_QTY);
int picColumnIndex = cursor.getColumnIndex(InventoryContract.ProductEntry.COLUMN_PRODUCT_PIC);
// Read the attributes from the Cursor for the current product
String prodName = cursor.getString(nameColumnIndex);
Double prodPrice = cursor.getDouble(priceColumnIndex);
int prodQty = cursor.getInt(qtyColumnIndex);
byte [] prodImg = cursor.getBlob(picColumnIndex);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inTempStorage = new byte[1024 * 32];
Bitmap bmp = BitmapFactory.decodeByteArray(prodImg, 0, prodImg.length, options);
//Update Views
nameTextView.setText(String.valueOf(prodName));
priceTextView.setText(prodPrice.toString());
qtyTextView.setText(String.valueOf(prodQty));
prodImageView.setImageBitmap(bmp);
}
}
When I try execute this code everything goes ok, but I see a blank image instead of both the selected pic and placer pic.
So I think that there is some problem with inserting data into db.
I am trying to store and retrieve image data in Sqlite Db
I do not recommend this. Store the images in files. Store data in the rows that identifies the files.
Then I set up an insert method to feed the db with these example data
You are storing a string in COLUMN_PRODUCT_PIC. You are not storing a byte[]. This is good, relative to my recommendation. This is bad relative to your data-retrieval code, where you are attempting to retrieve a byte[].

Pre-style the Color of a Substring in a Spinner Option [Android Studio]

I've got an SQLite database with a units table. The units table is set up with only two columns:
create table units (_id INTEGER PRIMARY KEY, desc TEXT)
Example data for a row in this table is:
_id: 4
desc: "Helix #5 [2231]"
The "[2231]" substring is important, and I'd like to change its color to a medium gray color. Id also prefer to do this to the data in the desc column, as opposed to manipulating it with java.
So, I query for the data:
/**
* Get all unit records for display in spinner
*/
public Cursor getAllUnitRecords(){
String sql = "select * from units order by `desc`";
return db.rawQuery(sql, null);
}
My spinner looks like this:
<Spinner
android:id="#+id/UnitSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spinnerMode="dropdown" />
And I get the data to the spinner like this:
// Prepare unit dropdown
Cursor units = db.getAllUnitRecords();
MatrixCursor unitsMatrixCursor = new MatrixCursor(new String[] { "_id", "desc" });
unitsMatrixCursor.addRow(new Object[] { 0, "" });
MergeCursor unitsMergeCursor = new MergeCursor(new Cursor[] { unitsMatrixCursor, units });
String[] unitsFrom = new String[]{"desc"};
int[] unitsTo = new int[]{android.R.id.text1};
Spinner unitSpinner = (Spinner) findViewById(R.id.UnitSpinner);
SimpleCursorAdapter unitAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_dropdown_item, unitsMergeCursor, unitsFrom, unitsTo, 0);
unitAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
unitSpinner.setAdapter(unitAdapter);
Since I'd like to color the "[2231]" substring a medium gray color, I thought I might be able to change the value of desc in the database, so that it looks like this:
"Helix #5 <font color='#6e737e'>[2231]</font>"
I did that only because I was searching the internet, and it seemed like it might work. Well, that doesn't work, as the tags are just output, instead of changing the color. What is wrong, and how can I fix it? I guess I'm open to a different solution if necessary, but this Android stuff is hard for me, as I don't work on it very often, so I was trying to go for the easiest solution.
UPDATE #1 ----------------------
So #MartinMarconcini was kind enough to point me in the right direction, and I copy and pasted his colorSpan method into my activity class to test it out. I then looked all around Stack Overflow for any clues as to how to modify the text of my spinner, and then how to modify the text that's in a SimpleCursorAdapter.
I found these questions with answers:
Android, using SimpleCursorAdapter to set colour not just strings
Changing values from Cursor using SimpleCursorAdapter
That gave me some ideas, so I tried to work with that:
// Prepare unit dropdown
Cursor units = db.getAllUnitRecords();
MatrixCursor unitsMatrixCursor = new MatrixCursor(new String[] { "_id", "desc" });
unitsMatrixCursor.addRow(new Object[] { 0, "" });
MergeCursor unitsMergeCursor = new MergeCursor(new Cursor[] { unitsMatrixCursor, units });
String[] unitsFrom = new String[]{"desc"};
int[] unitsTo = new int[]{android.R.id.text1};
Spinner unitSpinner = (Spinner) findViewById(R.id.UnitSpinner);
SimpleCursorAdapter unitAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_dropdown_item, unitsMergeCursor, unitsFrom, unitsTo, 0);
unitAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
/* NEW CODE STARTS HERE */
unitAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
public boolean setViewValue(View aView, Cursor aCursor, int aColumnIndex) {
if (aColumnIndex == 1) {
String desc = aCursor.getString(aColumnIndex);
TextView textView = (TextView) aView;
final Spannable colorized = colorSpan(desc);
textView.setText(TextUtils.isEmpty(colorized) ? desc + "a" : colorized + "b");
return true;
}
return false;
}
});
/* NEW CODE ENDS HERE */
unitSpinner.setAdapter(unitAdapter);
Notice I added the letter "a" if there was no text, and "b" if there was text. Sure enough, the "a" and "b" were added to my spinner items, but there was no color change! So, I am trying ... but could still use some help. Here is an image of what I'm seeing:
As mentioned in the comments, the presentation shouldn’t be tied to the logic. This is a presentation problem. You want to display a text and you want part of that text to be colored.
So, anywhere in your app where you need to display/present this text to the user, say…
someTextViewOrOtherWidget.setText(yourString);
…you’ll then have to call a method that does the coloring for you.
Example…
I’d move this code into a separate method/place for reuse and make it more re-usable by not hardcoding the [] and such,but this is how a simple example would look:
private Spannable colorSpan(final String text) {
if (TextUtils.isEmpty(text)) {
// can't colorize an empty text
return null;
}
// Determine where the [] are.
int start = text.indexOf("[");
int end = text.indexOf("]");
if (start < 0 || end < 0 || end < start) {
// can't find the brackets, can't determine where to colorize.
return null;
}
Spannable spannable = new SpannableString(text);
spannable.setSpan(
new ForegroundColorSpan(Color.BLUE)
, start
, end
, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
return spannable;
}
And you’d use it like…
String text = "Hello [123123] how are you?";
final Spannable colorized = colorSpan(text);
textView.setText(TextUtils.isEmpty(colorized) ? text : colorized);
I hope this gives you a better idea how to get started.
So, with a lot of help from #MartinMarconcini, I finally achieved what needed to be done, and so I wanted to leave "the answer" here, in case anyone else wants to see what needed to be done. I ended up making a custom cursor adapter, and although I'm still dumbfounded by the complexity of Android Studio, it works!
First, in the activity, the way SimpleCursorAdapter was being used ended up getting changed to the custom cursor adapter (which extends SimpleCursorAdapter).
These lines:
Spinner unitSpinner = (Spinner) findViewById(R.id.UnitSpinner);
SimpleCursorAdapter unitAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_dropdown_item, unitsMergeCursor, unitsFrom, unitsTo, 0);
unitAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
unitSpinner.setAdapter(unitAdapter);
Were replaced with these lines:
Spinner customUnitSpinner = (Spinner) findViewById(R.id.UnitSpinner);
UnitSpinnerCursorAdapter customUnitAdapter = new UnitSpinnerCursorAdapter(this, R.layout.unit_spinner_entry, unitsMergeCursor, unitsFrom, unitsTo, 0);
customUnitSpinner.setAdapter(customUnitAdapter);
I put the custom cursor adapter in its own file, and I put Martin's colorSpan method in there too (for now):
package android.skunkbad.xxx;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Color;
import android.support.v4.widget.SimpleCursorAdapter;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class UnitSpinnerCursorAdapter extends SimpleCursorAdapter {
private Context context;
private int layout;
public UnitSpinnerCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
this.context = context;
this.layout = layout;
}
/**
* newView knows how to return a new spinner option that doesn't contain data yet
*/
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
super.newView(context, cursor, parent);
Cursor c = getCursor();
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(layout, parent, false);
int descCol = c.getColumnIndex("desc");
String desc = c.getString(descCol);
final Spannable colorized = colorSpan(desc);
TextView unit_spinner_entry = (TextView) v.findViewById(R.id.custom_spinner_entry_desc);
if (unit_spinner_entry != null) {
unit_spinner_entry.setText(TextUtils.isEmpty(colorized) ? desc : colorized);
}
return v;
}
/**
* bindView knows how to take an existing layout and update it with the data pointed to by the cursor
*/
#Override
public void bindView(View view, Context context, Cursor cursor) {
super.bindView(view, context, cursor);
int descCol = cursor.getColumnIndex("desc");
String desc = cursor.getString(descCol);
final Spannable colorized = colorSpan(desc);
TextView unit_spinner_entry = (TextView) view.findViewById(R.id.custom_spinner_entry_desc);
if (unit_spinner_entry != null) {
unit_spinner_entry.setText(TextUtils.isEmpty(colorized) ? desc : colorized);
}
}
private Spannable colorSpan(final String text) {
if (TextUtils.isEmpty(text)) {
// can't colorize an empty text
return null;
}
// Determine where the [] are.
int start = text.indexOf("[");
int end = text.indexOf("]");
if (start < 0 || end < 0 || end < start) {
// can't find the brackets, can't determine where to colorize.
return null;
}
end++; /* Why do we even need this ? */
Spannable spannable = new SpannableString(text);
spannable.setSpan(
new ForegroundColorSpan(Color.rgb(100,100,100))
, start
, end
, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
return spannable;
}
}
Finally, I had to make a layout file for each entry in the spinner:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="#+id/custom_spinner_entry_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="#FFFFFF"
android:layout_marginLeft="10dp" />
</LinearLayout>
Thanks Martin! It might seem like nothing to you, but it was hard for me, and I couldn't have done it without your help.
One note: I had to put end++; in your colorSpan method, because for some reason it wasn't coloring the closing bracket.

SQLite image blob to string

I'm trying to get a blob from sqlite to string, please see below:
ArrayList<Sudentdb> list;
GridView gridView;
final String[] from = new String[] { SQLiteHelper._ID,
SQLiteHelper.NAME, SQLiteHelper.AGE, SQLiteHelper.PROFILE };
final int[] to = new int[] { R.id.rowid, R.id.txtName, R.id.studentage, R.id.profileimageV };
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.studentfragment, null);
gridView = (GridView) v.findViewById(R.id.gridView);
DBManager dbManager = new DBManager(getActivity());
dbManager.open();
Cursor cursor = dbManager.fetch();
list = new ArrayList<>();
SimpleCursorAdapter adapter = new SimpleCursorAdapter(getActivity(), R.layout.single_item, cursor, from, to, 0);
gridView.setAdapter(adapter);
I have tested returning only the text and it returns correctly, but with images I get a error:
android.database.sqlite.SQLiteException: unknown error (code 0): Unable to convert BLOB to string
I assume that I first have to get the image from the database?? Any help?
EDIT
I understand why you would think that this question is a duplicate of SimpleCursorAdapter how to show an image? but in that question the image is stored in the drawable folder and he is saving and calling the name of that image as a string into sqlite. In my case, the image is in the gallery and I save it as a blob into SQLite (In another activity) and now I am trying to get the blob back from the database and displaying it in a GridView.
So in the answer the following will not work for me:
int resID = getApplicationContext().getResources().getIdentifier(cursor.getString(columnIndex), "drawable", getApplicationContext().getPackageName());
IV.setImageDrawable(getApplicationContext().getResources().getDrawable(resID));
You can use this to get blob as byte[] from SQLITE
byte[] img = cursor.getBlob(cursor.getColumnIndex(IMG_SRC));
then convert byte[] to bitmap using below Util method ...
public static Bitmap getbitmap(byte[] img) {
return BitmapFactory.decodeByteArray(img, 0, img.length);
}

Android. Fetch data from database and then make network query. How to implement?

I'm pretty new to an Android development and currently trying to write an app that will show tomorrow's weather of multiple cities. Sorry for any incorrent termins that I might use in this question.
What I want to reach:
App will fetch data from local database, then build a HTTP query on the data fetched from a DB, get JSON response and form a list elements.
What I currently have:
Everything except SQL functionality.
Here is the snapshot of my main activity code. I use LoaderCallbacks<List<Weather>> to build URI with needed parameters in onCreateLoader(int i, Bundle bundle), send HTTP query and get the data via WeatherLoader(this, uriList), and form elements results in a List using WeatherAdapter.
public class WeatherActivity extends AppCompatActivity
implements LoaderCallbacks<List<Weather>>,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final int WEATHER_LOADER_ID = 1;
private WeatherAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.weather_activity);
ListView weatherListView = (ListView) findViewById(R.id.list);
mEmptyStateTextView = (TextView) findViewById(R.id.empty_view);
weatherListView.setEmptyView(mEmptyStateTextView);
mAdapter = new WeatherAdapter(this, new ArrayList<Weather>());
weatherListView.setAdapter(mAdapter);
...
weatherListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
Weather currentWeather = mAdapter.getItem(position);
Uri forecastUri = Uri.parse(currentWeather.getUrl());
Intent websiteIntent = new Intent(Intent.ACTION_VIEW, forecastUri);
startActivity(websiteIntent);
}
});
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
LoaderManager loaderManager = getLoaderManager();
loaderManager.initLoader(WEATHER_LOADER_ID, null, this);
} else {
View loadingIndicator = findViewById(R.id.loading_indicator);
loadingIndicator.setVisibility(View.GONE);
mEmptyStateTextView.setText(R.string.no_internet_connection);
}
}
#Override
public Loader<List<Weather>> onCreateLoader(int i, Bundle bundle) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
String tempUnit = sharedPrefs.getString(
getString(R.string.settings_temp_unit_key),
getString(R.string.settings_temp_unit_default));
List<String> uriList = new ArrayList<>();
/***
*
* Here we input cities for which we want to see the forecast
*
* ***/
List<String> cities = new ArrayList<>();
cities.add("London,uk");
cities.add("Kiev,ua");
cities.add("Berlin,de");
cities.add("Dubai,ae");
//For each city in the list generate URI and put it in the URIs list
for (String city : cities){
Uri baseUri = Uri.parse(OWM_REQUEST_URL);
Uri.Builder uriBuilder = baseUri.buildUpon();
uriBuilder.appendQueryParameter("q", city);
uriBuilder.appendQueryParameter("cnt", "16");
uriBuilder.appendQueryParameter("units", tempUnit);
uriBuilder.appendQueryParameter("appid", "some_key");
uriList.add(uriBuilder.toString());
}
return new WeatherLoader(this, uriList);
}
#Override
public void onLoadFinished(Loader<List<Weather>> loader, List<Weather> weatherList) {
mAdapter.clear();
// If there is a valid list of forecasts, then add them to the adapter's
// data set. This will trigger the ListView to update.
if (weatherList != null && !weatherList.isEmpty()) {
mAdapter.addAll(weatherList);
}
}
#Override
public void onLoaderReset(Loader<List<Weather>> loader) {
mAdapter.clear();
}
As you see, cities are "hardcoded" via List<String> cities = new ArrayList<>(); in onCreateLoader(int i, Bundle bundle). That's why I've decided to implement SQL storage of cities in my app. I know how to implement SQL functionality in android app using ContentProvider and CursorAdapter.
So what's the problem?
If I am correct we should use LoaderManager.LoaderCallbacks<Cursor> if we want to make a query to a local DB.
Unfortunately, I can't imagine how to merge current LoaderCallbacks<List<Weather>> and LoaderCallbacks<Cursor> in one activity to make it work as I want.
Actually, I want to change
List<String> cities = new ArrayList<>();
on something like
Cursor cursor = new CursorLoader(this, WeatherEntry.CONTENT_URI, projection, null, null, null); to build the URI on the results that CursorLoader returns.
But, we should make SQL query in separate thread and HTTP query also(!) in separate thread. Should we do nested threads/loaders (http query in a scope of sql fetching data and return a List<T>)? Even can't imagine how it's possible to do, if so...
Help me please, I've stuck!
Ok, it was not obvious to me at the first sight, but I finally solved the problem.
In the question above we had a list of cities that were hardcoded:
List<String> cities = new ArrayList<>();
cities.add("London,uk");
cities.add("Kiev,ua");
cities.add("Berlin,de");
cities.add("Dubai,ae");
Even if we assume that we will change it to a DB query, like this:
// Connect to a DB
...
Cursor forecastCitiesDataCursor = mDb.query(true, WeatherContract.WeatherEntry.TABLE_NAME, projection,
null, null, null,
null, null, null);
...
// Fetch data from cursor
...we will have that SQL query on the main thread. So we need a solution.
The best thing that I've found for me, it is put that SQL query in CustomLoader class and pass needed parameters in a constructor (in my case, it is SharedPreferences parameter to built an HTTP query).
Here is my code:
WeatherActivity.java
public class WeatherActivity extends AppCompatActivity implements LoaderCallbacks<List<Weather>>,
SharedPreferences.OnSharedPreferenceChangeListener {
...
#Override
public Loader<List<Weather>> onCreateLoader(int i, Bundle bundle) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
String tempUnit = sharedPrefs.getString(
getString(R.string.settings_temp_unit_key),
getString(R.string.settings_temp_unit_default));
return new WeatherLoader(this, tempUnit);
}
#Override
public void onLoadFinished(Loader<List<Weather>> loader, List<Weather> weatherList) {
// Hide loading indicator because the data has been loaded
View loadingIndicator = findViewById(R.id.loading_indicator);
loadingIndicator.setVisibility(View.GONE);
// Set empty state text to display "No forecasts found."
mEmptyStateTextView.setText(R.string.no_forecasts);
// Clear the adapter of previous forecasts data
mAdapter.clear();
// If there is a valid list of forecasts, then add them to the adapter's
// data set. This will trigger the ListView to update.
if (weatherList != null && !weatherList.isEmpty()) {
mAdapter.addAll(weatherList);
}
}
WeatherLoader.java
public class WeatherLoader extends AsyncTaskLoader<List<Weather>> {
...
// Pass parameters here from WeatherActivity
public WeatherLoader(Context context, String tmpUnit) {
super(context);
mTempUnit = tmpUnit;
}
#Override
protected void onStartLoading() {
forceLoad();
}
/**
* This is on a background thread.
*/
#Override
public List<Weather> loadInBackground() {
// List for storing built URIs
List<String> uriList = new ArrayList<>();
// List for storing forecast cities
List<String> cities = new ArrayList<>();
// Define a projection that specifies the columns from the table we care about.
...
Cursor forecastCitiesDataCursor = mDb.query(true, WeatherContract.WeatherEntry.TABLE_NAME, projection,
null, null, null,
null, null, null);
// Get list of cities from cursor
...
//For each city in the list generate URI and put it in the URIs list
for (String city : cities){
Uri baseUri = Uri.parse(OWM_REQUEST_URL);
Uri.Builder uriBuilder = baseUri.buildUpon();
uriBuilder.appendQueryParameter("q", city);
uriBuilder.appendQueryParameter("cnt", "16");
uriBuilder.appendQueryParameter("units", mTempUnit);
uriBuilder.appendQueryParameter("appid", /*some id*/);
uriList.add(uriBuilder.toString());
}
if (uriList == null) {
return null;
}
// Perform the network request, parse the response, and extract a list of forecasts.
List<Weather> forecasts = QueryUtils.fetchForecastData(uriList);
return forecasts;
}
So what we've got?
We've implemented persistent data storage within the work with ArrayAdapter that are used to do an HTTP query then. SQL query are on the separate thread and we'll have no problem with app performance.
Hope that solution will help somebody, have a nice day!

Get spacial content of selected item in listview in android

I am new with android programing and I have a problem with list view
In my app I have to read data from database (name,ID,year) and then add them to listview after that user must select one of
the items and in a new activity again I read data from db and list some of the other Items based on user's selection
Ol at this time In my first activity I read data and add them to listview..To select I must define a listener..right?
I define it like this code
enter code here #Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_read_book);
String SDcardPath = Environment.getExternalStorageDirectory().getPath();
String DbPath = SDcardPath + "/Tosca/" + "persian_poem.db";
ListView list = (ListView) findViewById(R.id.list_poet_name);
try {
db = SQLiteDatabase.openDatabase(DbPath,null,SQLiteDatabase.CREATE_IF_NECESSARY);
getData();
db.close();
}
catch (SQLiteException e) {
Toast.makeText(this, e.getMessage(), 1).show();
}
list.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View v, int position,
long id) {
// TODO Auto-generated method stub
ListView list = (ListView) findViewById(R.id.list_poet_name);
Log.i(TAG, "Listview get Item Pos");
Peot_ID.putString ("Peot_ID", (String) list.getItemAtPosition(position));
Intent Book_list_intent = new Intent (Read.this,Book_list.class);
Book_list_intent.putExtras(Peot_ID);
startActivity(Book_list_intent);
}
});
}
private void getData() {
try {
//txtMsg.append("\n");
// obtain a list of from DB
String TABLE_NAME = "classicpoems__poet_contents";
String COLUMN_ID = "poet_id";
String _ID = "_id";
String COLUMN_NAME = "poet_name";
String COLUMN_CENTURY = "century_start";
String [] columns ={_ID,COLUMN_ID,COLUMN_NAME,COLUMN_CENTURY};
Cursor c = db.query(TABLE_NAME,columns,null, null, null, null, COLUMN_ID);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, c,
new String[] {COLUMN_NAME,COLUMN_CENTURY}, new int[] {android.R.id.text1,android.R.id.text2}, 0);
ListView list = (ListView) findViewById(R.id.list_poet_name);
list.setAdapter(adapter);
} catch (Exception e) {
Toast.makeText(this, e.getMessage(), 1).show();
}
}
But here I have a problem..I want to send data of peot_id (Its deffrent from _id column in db) to next activity..Bt I mentioned that
with this code I can get whole row of selected item and I just want part of it(peot_id ) can you help me how to get just Peot_ID from selected
list item?
and I have another question..
As you see in my code I must refer to one spasial listview several times..each time I defined it by this code
enter code hereListView list = (ListView) findViewById(R.id.list_poet_name);
How can I define this listviwe one time and use it in several places in my code?sth like a public variable or sth like that
Thanks for your help.
As you see in my code I must refer to one spasial listview several
times..each time I defined it by this code
No. Just create one global ListView variable list and simply you can access to it from everywhere in your Activity. There is no need to declaring and initialising ListView again in OnItemClick() method.
I want to send data of peot_id (Its deffrent from _id column in db) to
next activity..Bt I mentioned that with this code I can get whole row
of selected item and I just want part of it(peot_id ) can you help me
how to get just Peot_ID from selected list item?
You are using Android's defined basic layout
android.R.layout.simple_list_item_2
I suggest you to create own XML file for row and then simply get whole View from ListView and from View you can get only ID.
Example:
listrow.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp"
android:background="#drawable/addresses_list_selector"
>
<TextView
android:id="#+id/id_column"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="#+id/name_column"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/id_column"
/>
<TextView
android:id="#+id/century_column"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/name_column"
/>
</RelativeLayout>
Then an usage with CursorAdapter:
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.listrow, c,
new String[] {COLUMN_ID, COLUMN_NAME, COLUMN_CENTURY},
new int[] {R.id.id_column, R.id.name_column, R.id.century_column}, 0);
And then for getting ID from row:
public void onItemClick(AdapterView<?> parent, View v, int position,
long id) {
TextView id = (TextView) v.findViewById(R.id.id_column);
if (id != null) {
String idString = id.getText().toString();
}
}
Note:
If you still want to use android's predefined layout, you need to pass into String[] from ID_COLUMN and then access to ID from row via row.findViewById(<id>);
String[] from = {ID_COLUMN, NAME_COLUMN};
int[] to = {android.R.id.text1, android.R.id.text2};
TextView id = v.findViewById(android.R.id.android.R.id.text1);
String idString = id.getText().toString();
You do query like this to get a Particular column record alone :
Cursor mCursor = mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
KEY_NAME, KEY_DESIGNATION}, KEY_ROWID + "=" + yourPrimaryKey, null,
null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
}
I personally prefer to use onListItemclick() method like that
//do not forget to override - very important
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
//TODO what you what you have here the vars position - position of the selected item in list
// and also the id so you can easy trace what selection done the user
// you can play with this
}

Categories

Resources