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!
I have a listview that loads information from sqlite database. The information should load image this way:
This is the code for the listview activity:
private void populateListViewFromDB() {
Cursor cursor = myDb.getAllRows();
// Allow activity to manage lifetime of the cursor.
// DEPRECATED! Runs on the UI thread, OK for small/short queries.
startManagingCursor(cursor);
// Setup mapping from cursor to view fields:
String[] fromFieldNames = new String[]
{DBAdapter.KEY_DATE, DBAdapter.KEY_IMG, DBAdapter.KEY_FAVCOLOUR};
int[] toViewIDs = new int[]
{R.id.item_date, R.id.item_icon, R.id.item_kcal};
// Create adapter to may columns of the DB onto elemesnt in the UI.
SimpleCursorAdapter myCursorAdapter =
new SimpleCursorAdapter(
this, // Context
R.layout.item_layout, // Row layout template
cursor, // cursor (set of DB records to map)
fromFieldNames, // DB Column names
toViewIDs // View IDs to put information in
);
// Set the adapter for the list view
ListView myList = (ListView) findViewById(R.id.listViewFromDB);
myList.setAdapter(myCursorAdapter);
}
Theoretically, I'm trying to save the string "snapPath" to the field "KEY_IMG" and load the image into imageview "item_icon". If the user does not snap a photo, by default, the imageview will load a drawable instead.
At the Add activity page, I added a string and save that string to the database:
String snapPath = "res/drawable-xxhdpi/ic_launcher.png"; //by default it will load a drawable
myDb.insertRow(date, snapPath, kcal+" kcal"); //saves into database
Also in Add activity page, the code for capturing and saving the image into my phone:
private void doTakePicture() {
// create a File object for the parent directory
File newDir = new File( Environment.getExternalStorageDirectory(), "/myFoodDiary/snaps");
newDir.mkdirs();
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
//for file name
Date cDate = new Date();
final String fDate = new SimpleDateFormat("yyyyMMMd").format(cDate);
final String fTime = new SimpleDateFormat("HHmmss").format(cDate);
String snapName = "mFD-" + fDate + fTime + ".jpg";
fileJpeg = new File(newDir, snapName);
snapPath = "/myFoodDiary/snaps/"+String.valueOf(snapName);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(fileJpeg));
startActivityForResult(takePictureIntent, TAKE_PIC_REQ);
}
Thanks in advance!
To customize whether to draw a bitmap from storage or a drawable from your resources, you can't use SimpleCursorAdapter anymore. You need to derive a new class from CursorAdapter and use that.
Conditionally loading the images is not as trivial as it seems. I recommend using the Android Query library image loading methods which support placeholders, fallbacks, and asynchronous loading - just what you need.
I am trying to use some data from my database, but function only use data from first entry. What should I add to go through all the entries from database?
public void drawdayplan(){
DatabaseConnector dbConnector = new DatabaseConnector(Clock.this);
Intent a = getIntent();
String d_m_y = a.getStringExtra("d_m_y");
dbConnector.open();
Cursor result = dbConnector.gethour(d_m_y);
if(result.moveToFirst()){
int hourfromIndex = result.getColumnIndex("inthourfrom");
int hourtoIndex = result.getColumnIndex("inthourto");
int colourIndex = result.getColumnIndex("colour");
hourfrom = result.getInt(hourfromIndex);
hourto = result.getInt(hourtoIndex);
colour = result.getString(colourIndex);
// here are some steps with painting which are using this variables
}
}
result.close();
dbConnector.close();
}
But function only use data from first entry.
You never ask for more than the first row. Call moveToNext() in a loop like this:
while(result.moveToNext()){
// Read each row
}
In my scenario i am having 3 records in sqlite which were fetched from websercive that contains id,name,description and image now i want to display the first record and second one after flipping the first record.and 3 one after second record flips..
What i have done so far is
while (cursor.moveToNext())
{
String temp_bookid = cursor.getString(0);
Log.i("temp_bookid",temp_bookid);
String temp_desc=cursor.getString(1);
byte[] temp_image1 = cursor.getBlob(2);
byte[] temp_image2 = cursor.getBlob(3);
String temp_id=cursor.getString(4);
String temp_name = cursor.getString(5);
String temp_bname=cursor.getString(6);
mapId.add(temp_id);
mapBname.add(temp_bname);
mapBookId.add(temp_bookid);
mapName.add(temp_name);
mapDesc.add(temp_desc);
map2.add(temp_image2);
map1.add(temp_image1);
Log.i("temp_id of the page in sqlite", temp_id);
TextView txtDesc = (TextView) findViewById(R.id.tDescription);
text = (TextView) findViewById(R.id.tTitle);
text.setText(temp_name);
txtDesc.setText(temp_desc);
ImageView img = (ImageView)findViewById(R.id.smallImage);
img.setImageBitmap(BitmapFactory.decodeByteArray(temp_image1, 0,temp_image1.length));
txtName1 = (TextView) findViewById(R.id.tvName);
txtName1.setText(temp_bname);
break;
}
mContext = this;
vf = (ViewFlipper) this.findViewById(R.id.vfShow);
vf.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(final View view, final MotionEvent event) {
detector.onTouchEvent(event);
Log.i("ew","ae");
return true;
}
});
I am able to display first record data when i set break; statement otherwise it is displaying last record data..How can proceed for this approach?
First fetch all the records and store in List<YourDataClass> myList object from onCreate method. As I have just giving you the examples
Now after fetching the data and stored in your list object you need to implement flipper change event so when flipper was change get the current position and using this position get the data as object from your list object. As you move right/left it will giving you the current position and you just need to use this position to fetch the records from list object
For example
Your POJO class will store the data MyData.java
public class MyData{
public long id = -1;
public String name = "";
public String description = "";
public String imagePath = ""; /// I don't know you want exactly, you need to implement image as per your requirement.
}
In your activity
private List<MyData> myList = new ArrayList<MyData>();
oncreate(){
fetchData(); // you need to implement this in AsyncTask so UI was not block just implement AsyncTask and call this method in doInBackground().
// after this you can implment View flipper and add view and set the data by fetch the list object using current view flipper position.
}
private void fetchData(){
while (cursor.moveToNext()){
MyData myData = new MyData();
myData.id = cursor.getLong(0);
myData.desc=cursor.getString(1);
myData.name = cursor.getString(5);
list.add(myData);
myData = null;
}
}
In my scenario i am having 3 records in sqlite which were fetched from websercive that contains id,name,description and image now i want to display the first record and second one after flipping the first record.and 3 one after second record flips..
What i have done so far is
while (cursor.moveToNext())
{
String temp_bookid = cursor.getString(0);
Log.i("temp_bookid",temp_bookid);
String temp_desc=cursor.getString(1);
byte[] temp_image1 = cursor.getBlob(2);
byte[] temp_image2 = cursor.getBlob(3);
String temp_id=cursor.getString(4);
String temp_name = cursor.getString(5);
String temp_bname=cursor.getString(6);
mapId.add(temp_id);
mapBname.add(temp_bname);
mapBookId.add(temp_bookid);
mapName.add(temp_name);
mapDesc.add(temp_desc);
map2.add(temp_image2);
map1.add(temp_image1);
Log.i("temp_id of the page in sqlite", temp_id);
TextView txtDesc = (TextView) findViewById(R.id.tDescription);
text = (TextView) findViewById(R.id.tTitle);
text.setText(temp_name);
txtDesc.setText(temp_desc);
ImageView img = (ImageView)findViewById(R.id.smallImage);
img.setImageBitmap(BitmapFactory.decodeByteArray(temp_image1, 0,temp_image1.length));
txtName1 = (TextView) findViewById(R.id.tvName);
txtName1.setText(temp_bname);
break;
}
mContext = this;
vf = (ViewFlipper) this.findViewById(R.id.vfShow);
vf.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(final View view, final MotionEvent event) {
detector.onTouchEvent(event);
Log.i("ew","ae");
return true;
}
});
I am able to display first record data when i set break; statement otherwise it is displaying last record data..How can proceed for this approach?
First fetch all the records and store in List<YourDataClass> myList object from onCreate method. As I have just giving you the examples
Now after fetching the data and stored in your list object you need to implement flipper change event so when flipper was change get the current position and using this position get the data as object from your list object. As you move right/left it will giving you the current position and you just need to use this position to fetch the records from list object
For example
Your POJO class will store the data MyData.java
public class MyData{
public long id = -1;
public String name = "";
public String description = "";
public String imagePath = ""; /// I don't know you want exactly, you need to implement image as per your requirement.
}
In your activity
private List<MyData> myList = new ArrayList<MyData>();
oncreate(){
fetchData(); // you need to implement this in AsyncTask so UI was not block just implement AsyncTask and call this method in doInBackground().
// after this you can implment View flipper and add view and set the data by fetch the list object using current view flipper position.
}
private void fetchData(){
while (cursor.moveToNext()){
MyData myData = new MyData();
myData.id = cursor.getLong(0);
myData.desc=cursor.getString(1);
myData.name = cursor.getString(5);
list.add(myData);
myData = null;
}
}