In my Android app i have an Activity with tabs and each tab changes to a different Fragment, each with several fields the user should fill. The user can then submit all data by clicking a button in the action bar.
Is there an easy way to access and store all data from the different Fragments at action bar button click? Am i missing something here?
If I understood your problem right, I'd see two possible solutions:
1) To copy the data from the fragments to a class extending the Application class, so that class work as a placeholder for global variables. You only would need to capture the input events on the fragments to update the class, or to capture the fragment transition to copy the data from it to the Application
2) You can inflate your fragments' views into the main activity one by setting the attachToRoot flag. Then it should be possible to find them with findViewById in the main activity
It is unclear on what you mean by save. If you want to store the data locally on the phone:
Android Storage options
From these I strongly recommend SQLite for more complex data and Shared Preferences for simple (String or int) type data.
When You Entering the data in First Fragment that all data you can store it into one String by comma separated value and then access that values into second fragment and split that string and store it into Sqlite DB .
If I understand correctly you want to save data to your database from various parts of your app. I would suggest you use ContentProvider/ContentResolver for this. With this approach you would implement a ContentProvider which interacts with your database and from anywhere else in the app you would use a ContentResolver to interact with the ContentProvider.
In your ContentProvider you would have to implement the methods, query(), insert(), delete(), update(), onCreate() and getType(). Uris are used to identify what you want to insert or select from the ContentProvider. You can use a static UriMatcher to make parsing of the Uris very simple. I will give you an example on how to implement the query method with a sqlite database:
This is how you would define an UriMatcher in your Provider:
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
// Here you define your Uris, in this case for a table I called TABLE_ONE.
// If you want to know what each of these parameters means I suggest you view the documentation
sURIMatcher.addURI(AUTHORITY, BASE_PATH, TABLE_ONE_ID);
}
// I suggest you define all constants like the AUTHORITY and BASEPATH and Uri's in a Contract class.
// You create the Uris from the value of AUTHORITY and BASE_PATH like this:
public static final Uri TABLE_ONE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
And this is a rudimentary implementation of the query method:
public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor c = null;
// The UriMatcher gives you the id of the uri if you added it like in the above example
int uriId = sURIMatcher.match(uri);
switch (uriId ) {
case TABLE_ONE_ID:
SQLiteDatabase db = database.getWritableDatabase();
c = db.query(TABLE_ONE_NAME, projecton, selection, selectionArgs, sortOrder);
c.setNotificationUri(getContext().getContentResolver(), uri);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
return c;
}
And then in your app you can select data from the db using a ContentResolver like this:
// Note that getContentResolver is a method of Activity, in a Fragment you have to call getActivity().getContentResolver()
getContentResolver().query(TABLE_ONE_URI, projection, selection, selectionArgs, sortOrder);
Here is the link to a complete Tutorial about pretty much anything important regarding SQLite and ContentProviders in Android:
http://www.vogella.com/articles/AndroidSQLite/article.html
Here you can find the official guide from Google on how to implement a ContentProvider:
http://developer.android.com/guide/topics/providers/content-provider-creating.html
Related
Veracode Static Scan report points SQL Injection flaw in my Content Provider implementation.
Previously, I posted this question related to all my doubts regarding this flaw.
And after few discussions I came to a conclusion that there might be chances of it being a false positive in the report. Because according to what I researched and read, I was following the security guidelines mentioned in Android docs and other referenced sources to avoid SQL Injection.
There is suggestion everywhere to perform at least some input validation on the data passed to SQL queries.I want to cover this possibility which be the reason of flaw.
Everyone is asking me to sanitize data before passing to query.
How do I exactly sanitize variables passed to selectionArgs array passed to delete(), update() method of Content Provider?
Will DatabaseUtils.sqlEscapeString() be sufficient?
Please suggest!
Here's the implementation where I need to sanitize the variable:
public Loader<Cursor> onCreateLoader(int id, Bundle b) {
switch (id) {
case THOUGHT_LOADER:
return new CursorLoader(getActivity(), NewsFeedTable.CONTENT_URI, NewsFeedTable.PROJECTION, NewsFeedTable._id + "=?", new String[]{tid}, null);
case COMMENT_LOADER:
return new CursorLoader(getActivity(), CommentTable.CONTENT_URI, CommentTable.PROJECTION, CommentTable.COLUMN_TID + "=?", new String[]{tid}, null);
default:
return null;
}
}
Report points to the flaw :Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWEID 89) at this line
deleted = db.delete(BulletinTable.TABLE_NAME, selection, selectionArgs); in the below code:
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
if (uri.equals(Contract.BASE_CONTENT_URI)) {
deleteDatabase();
return 1;
}
SQLiteDatabase db = openHelper.getWritableDatabase();
int deleted = 0;
switch (matcher.match(uri)) {
case BULLETIN:
deleted = db.delete(BulletinTable.TABLE_NAME, selection, selectionArgs);
break;
case CLASSROOMS:
deleted = db.delete(ClassroomsTable.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
if (deleted > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return deleted;
}
Values in the selectionArgs array never need to be sanitized, because they cannot be interpreted as SQL commands (that's the whole point of having separate parameter values).
The purpose of sqlEscapeString() is to format a string so that it can be put into an SQL command (i.e., escape single quotes; all other characters have no meaning inside an SQL string). But when you know that there is a string, you should selectionArgs instead, so this function is not helpful.
You need to sanitize only strings that end up in the SQL command itself. In this case, this would be selection. If this value comes from the user, or from some other app, then you have no control over how much stuff your DELETE statement actually does (it could call SQL functions, or execute subqueries that access other parts of the database).
For practical purposes, it is not possible to sanitize strings that are intended to contain SQL commands, because then you would need a full SQL parser. If your content provider is available for external code, you should allow deleting specific items only through the URI, and disallow custom selections.
I need to retrieve all data for a single contact which was chosen by a user (Action.Pick). I want to use LOOKUP KEY as suggested by developer.android.com. Can I do it by a simple query without creating Loader etc. as suggested by the documentation ?
I wrote this method for my application:
public static Cursor getContactCursorByLookUpKey(Context context,String lookUpKey)
{
ContentResolver contentResolver = context.getContentResolver();
Uri lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI,lookUpKey);
return contentResolver.query(lookupUri,null,null,null,null);
}
To get the data simply read data from the cursor. For example :
Cursor data = ContactManager.getContactCursorByLookUpKey(ContactDetailsActivity.this,lookUpKey);
String name = data.getString(data.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
String photoPath = data.getString(data.getColumnIndex(ContactsContract.Contacts.PHOTO_URI));
You can query the Contacts provider using ContentResolver directly, if you have the READ_CONTACTS permission. You will receive a cursor with multiple rows of raw contact details (phones, emails, etc.). Iterate over the cursor to read them and don't forget to close the cursor.
I'm working with search suggestion framework and here is the case. I get some query string and make query from content resolver, but problem is that I can't limit the results. Found several solutions but they dont work for me.
My content provider is extending SearchRecentSuggestionsProvider and declared in manifest, here query uri
Uri URI = Uri.parse("content://" + AUTHORITY + "/search_suggest_query");
sol 1:
adding query parameter to uri
SearchSuggestionProvider.URI.buildUpon().appendQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT, String.valueOf(5)).build()
sol 2:
adding limit in sortOrder query parameter
getContentResolver().query(URI, null, "?", new String[]{searchQuery}, "_id asc limit 5");
In both cases query returns all rows from search suggestions. Does anyone know how to limit such a query?
Actually, we were searching in the wrong place.
After all this investigations, finally I found the trick.
On the results screen (Where we save the query to the search SearchRecentSuggestions) we call this method
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
SearchSuggestionProvider.AUTHORITY, SearchSuggestionProvider.MODE);
suggestions.saveRecentQuery(query, null);
SearchRecentSuggestions was the responsible to save this query to the content provider.
This class has a protected method called truncateHistory() with this documentation
Reduces the length of the history table, to prevent it from growing
too large.
So the solution is simply, create a custom class, override this method, and call the super implementation with the required limit.
Here is the example
public class SearchRecentSuggestionsLimited extends SearchRecentSuggestions {
private int limit;
public SearchRecentSuggestionsLimited(Context context, String authority, int mode, int limit) {
super(context, authority, mode);
this.limit = limit;
}
#Override
protected void truncateHistory(ContentResolver cr, int maxEntries) {
super.truncateHistory(cr, limit);
}
}
Below is the query method in the base class SearchRecentSuggestionsProvider in the framework
/**
* This method is provided for use by the ContentResolver. Do not override, or directly
* call from your own code.
*/
// TODO: Confirm no injection attacks here, or rewrite.
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Potentially you can override it and implement the limiting - but as you can see the comment above it clear states "do not override".
In the existing method no limiting is enforced.
However I see in some of the other answers on stack-overflow and other sites that people do override it.
Example :
Use SearchRecentSuggestionsProvider with some predefined terms?
Based on this I think you can try it out - override the query method and add your own implementation which supports limiting the results. Parse the SearchManager.SUGGEST_PARAMETER_LIMIT that you have used in the query method and use it to limit the results returned.
I have two applications and I want to create a query from App A to get App B's info.
This is a very simple process as shown below, but i wanted to double check with the community and get some constructive criticism. Is there a better way?
Thank you.
This code gets the name of someone in my DB.
private Cursor getData() {
final Uri uri = Uri.parse("content://" + AUTHORITY + "/" + TABLE_PATH);
String[] projection = new String[] { "_id", "foo" };
String selection = null;
String[] selectionArgs = null;
String sortOrder = null;;
ContentResolver resolver = getContentResolver();
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
}
Well, it should work if the system is aware of a content provider that has the authority you've specified, and that provider recognizes TABLE_PATH, and the provider recognizes "_id" and "foo" as valid column names.
However, you seem to be asking several different questions:
Is this a safe way to query another app's content provider? Oh yes; It is by far the most preferred way to get data from another app.
"This code gets the name of someone in my DB". Actually, that's a slight misstatement. The code runs a query against the content provider that has the authority AUTHORITY, and against an entity in the provider that is named TABLE_PATH. No database is necessary, although content providers often store their data in an SQLite database. All that the CP has to do is establish an authority and respond to incoming content URIs.
I am learning Android and I am stuck on an issue involving calling a custom content provider. I have been using an example in an instructional book and although it describes how to create the custom provider there is no clear example how to call the specific methods in it. I am specifically looking into how to delete a single record from the custom content provider.
Here is the code for the custom content provider (EarthquakeProvider.java):
#Override
public int delete(Uri uri, String where, String[] whereArgs) {
int count;
switch (uriMatcher.match(uri)) {
case QUAKES:
count = earthquakeDB.delete(EARTHQUAKE_TABLE, where, whereArgs);
break;
case QUAKE_ID:
String segment = uri.getPathSegments().get(1);
count = earthquakeDB.delete(EARTHQUAKE_TABLE, KEY_ID + "="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND ("
+ where + ')' : ""), whereArgs);
break;
default: throw new IllegalArgumentException("Unsupported URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
I am trying to call the delete method from the main activity to delete a single entry, not the entire database. I want to use about an OnLongClickListener for the selected record that is displayed in a array list view in the main activity.
This is what I have come up with I have so far in my main activity for this method:
earthquakeListView.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView _av, View _v, int _index,
long arg3) {
ContentResolver cr = getContentResolver();
cr.delete(earthquakeProvider.CONTENT_URI, null, null);
return false;
}
I know the above code doesn't work, but this is as close as I could get with my current understanding.
Any help on this would be very much appreciated.
cr.delete(earthquakeProvider.CONTENT_URI, null, null);
This is your problem. First, some context:
Content URIs: (source)
content://authority/path/##
The number at the end is optional. If present, the URI references a specific row in the database where row._id=(the number). If absent, it references the table as a whole.
the delete() call accepts a URI, a where clause, and a set of strings which get substituted in. Example: Say you have a database of people.
cr.delete(
Person.CONTENT_URI,
"sex=? AND eyecolor=?",
new String[]{"male", "blue"});
Will search the entire person table, and delete anyone whose sex is male and whose eye color is blue.
If the where clause and where values are null, then the delete() call will match every row in the table. This causes the behavior you see.
There are two methods to specify the row you want:
First option, you could append the number to the URI:
cr.delete(
EarthquakeProvider.CONTENT_URI.buildUpon().appendPath(String.valueOf(_id)).build(),
null, null);
This restricts the URI to a specific row, and the path will be through your case QUAKE_ID: statement and so will only delete one row no matter what.
Second option, you could use a where clause:
cr.delete(EarthquakeProvider.CONTENT_URI, "_id=?", String.valueOf(_id)));
Either way, you will restrict the delete to a single row, as you need it to. The latter makes for prettier code, but the former is more efficient, due to the way the ContentProvider and ContentObservers work.
As a last note: In your ContentProvider you need to add a call to
ContentResolver.notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork). This helps notify cursors to re-fetch the database query and helps out a lot with automation.