Before i used an ArrayList for this, but because of duplicate Album issues (which i resolved before API29 by using DISTINCT and GROUP BY statements) which are not allowed anymore to use inside a query.
I got values like this in my RecyclerViews Adapter: myArrayList.get(position).getAlbum();
But now that i'm using a HashMap, how can i get a value by position which i get from the adapter?
Code for adding values in Hashmap
HashMap<Integer, Album> albums = new HashMap<>();
String[] projection2 = { MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.IS_MUSIC};
String selection = MediaStore.Audio.Media.IS_MUSIC + "!=0";
String sort = MediaStore.Audio.Media.ALBUM + " COLLATE NOCASE ASC";
cursor = resolver.query(musicUri, projection2, selection, null, sort);
try {
if (cursor != null) {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
int columnAlbumId = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID);
int columnAlbumName = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
String albumId = cursor.getString(columnAlbumId);
String albumName = cursor.getString(columnAlbumName);
if(albums.containsKey(albumId) == false){
Album album = new Album(albumId, albumName);
albums.put(albumId, album);
}
cursor.moveToNext();
}
}
}catch (Exception e){
Log.e(TAG, "Exception caught when creating ALBUM!", e);
throw new Exception();
}
By definition the HashMap is not sorted so how about a SortedMap? An implementation of a SortedMap is a TreeMap which sorts the entries according to keys (or a Comparator).
I believe that suits you since your map has integers as keys.
Edit:
You can use a List or whatever collection suits you in your adapter. For example keep a reference to the ids and the albums which is the data that you're interested in displaying through the adapter.
private int[] ids;
private Album[] albums;
As you're passing the data (included in a map) to your adapter then you could extract that data and place it in the array containers so you can take advantage of the index. For example,
public MyAdapter(Map<Integer,Album> data){
ids = new int[map.size()];
albums = new Album[map.size()];
int i = 0;
for (Map.Entry<Integer,Album> e : map.entrySet()){
ids[i] = e.getKey();
albums[i++] = e.getValue();
}
}
Now that you have your arrays you can sort them also if you like and in case you want to grab the 3rd album and its id all you need to do is,
int id = ids[2];
Album album = albums[2];
Related
I have a categorized Notes view, let say the first categorized column is TypeOfVehicle the second categorized column is Model and the third categorized column is Manufacturer.
I would like to collect only the values for the first category and return it as json object:
I am facing two problems:
- I can not read the value for the category, the column values are emptry and when I try to access the underlying document it is null
the script won't hop over to the category/sibling on the same level.
can someone explain me what am I doing wrong here?
private Object getFirstCategory() {
JsonJavaObject json = new JsonJavaObject();
try{
String server = null;
String filepath = null;
server = props.getProperty("server");
filepath = props.getProperty("filename");
Database db;
db = utils.getSession().getDatabase(server, filepath);
if (db.isOpen()) {
View vw = db.getView("transport");
if (null != vw) {
vw.setAutoUpdate(false);
ViewNavigator nav;
nav = vw.createViewNav();
JsonJavaArray arr = new JsonJavaArray();
Integer count = 0;
ViewEntry tmpentry;
ViewEntry entry = nav.getFirst();
while (null != entry) {
Vector<?> columnValues = entry.getColumnValues();
if(entry.isCategory()){
System.out.println("entry notesid = " + entry.getNoteID());
Document doc = entry.getDocument();
if(null != doc){
if (doc.hasItem("TypeOfVehicle ")){
System.out.println("category has not " + "TypeOfVehicle ");
}
else{
System.out.println("category IS " + doc.getItemValueString("TypeOfVehicle "));
}
} else{
System.out.println("doc is null");
}
JsonJavaObject row = new JsonJavaObject();
JsonJavaObject jo = new JsonJavaObject();
String TypeOfVehicle = String.valueOf(columnValues.get(0));
if (null != TypeOfVehicle ) {
if (!TypeOfVehicle .equals("")){
jo.put("TypeOfVehicle ", TypeOfVehicle );
} else{
jo.put("TypeOfVehicle ", "Not categorized");
}
} else {
jo.put("TypeOfVehicle ", "Not categorized");
}
row.put("request", jo);
arr.put(count, row);
count++;
tmpentry = nav.getNextSibling(entry);
entry.recycle();
entry = tmpentry;
} else{
//tmpentry = nav.getNextCategory();
//entry.recycle();
//entry = tmpentry;
}
}
json.put("data", arr);
vw.setAutoUpdate(true);
vw.recycle();
}
}
} catch (Exception e) {
OpenLogUtil.logErrorEx(e, JSFUtil.getXSPContext().getUrl().toString(), Level.SEVERE, null);
}
return json;
}
What you're doing wrong is trying to treat any single view entry as both a category and a document. A single view entry can only be one of a category, a document, or a total.
If you have an entry for which isCategory() returns true, then for the same entry:
isDocument() will return false.
getDocument() will return null.
getNoteID() will return an empty string.
If the only thing you need is top-level categories, then get the first entry from the navigator and iterate over entries using nav.getNextSibling(entry) as you're already doing, but:
Don't try to get documents, note ids, or fields.
Use entry.getColumnValues().get(0) to get the value of the first column for each category.
If the view contains any uncategorised documents, it's possible that entry.getColumnValues().get(0) might throw an exception, so you should also check that entry.getColumnValues().size() is at least 1 before trying to get a value.
If you need any extra data beyond just top-level categories, then note that subcategories and documents are children of their parent categories.
If an entry has a subcategory, nav.getChild(entry) will get the first subcategory of that entry.
If an entry has no subcategories, but is a category which contains documents, nav.getChild(entry) will get the first document in that category.
I'm retrieving list of distinct folders list having video files with number of videos in each folder, and this is working fine in devices having Android P and below, but when I run on devices having Android Q the app crashes.
How can I make it work for devices running Android Q
java.lang.IllegalArgumentException: Invalid column DISTINCT
bucket_display_name
Logcat:
java.lang.IllegalArgumentException: Invalid column DISTINCT bucket_display_name
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:170)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
at android.content.ContentProviderProxy.query(ContentProviderNative.java:423)
at android.content.ContentResolver.query(ContentResolver.java:944)
at android.content.ContentResolver.query(ContentResolver.java:880)
at android.content.ContentResolver.query(ContentResolver.java:836)
at com.aisar.mediaplayer.fragments.VideoFolderFragment$MediaQuery.getAllVideo(VideoFolderFragment.java:364)
at com.aisar.mediaplayer.fragments.VideoFolderFragment$VideosLoader.loadVideos(VideoFolderFragment.java:434)
at com.aisar.mediaplayer.fragments.VideoFolderFragment$VideosLoader.access$1100(VideoFolderFragment.java:413)
at com.aisar.mediaplayer.fragments.VideoFolderFragment$5.run(VideoFolderFragment.java:189)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:289)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
My Code:
public class MediaQuery {
private Context context;
private int count = 0;
private Cursor cursor;
List<ModelVideoFolder> videoItems;
public MediaQuery(Context context) {
this.context = context;
}
public List<ModelVideoFolder> getAllVideo(String query) {
String selection = null;
String[] projection = {
"DISTINCT " + MediaStore.Video.Media.BUCKET_DISPLAY_NAME,
MediaStore.Video.Media.BUCKET_ID
};
cursor = context.getContentResolver().query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
null,
query);
videoItems = new ArrayList<>();
ModelVideoFolder videoItem;
while (cursor.moveToNext()) {
videoItem = new ModelVideoFolder(
"" + cursor.getString(1),
"" + cursor.getString(0),
"",
"",
"" + getVideosCount(cursor.getString(1))
);
videoItems.add(videoItem);
}
return videoItems;
}
public int getVideosCount(String BUCKET_ID) {
int count = 0;
String selection = null;
String[] projection = {
MediaStore.Video.Media.BUCKET_ID,
};
Cursor cursor = getActivity().getContentResolver().query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
null,
null);
while (cursor.moveToNext()) {
if (BUCKET_ID.equals(cursor.getString(0))) {
//add only those videos that are in selected/chosen folder
count++;
}
}
return count;
}
}
This is due to the restrictions in Android Q.
In Android Q the projection must contain only valid column names without additional statements. Is not possible anymore to embed any type of SQL statement in the projection.
So, projections such as "DISTINCT " + YourColumName, or even trying to make a column alias such as "ExistingColumnName AS AnotherName" will always fail.
The workaround is to perform multiple queries (cursors) to get your required metrics, and construct with the results a CursorWrapper or MatrixCursor.
See the next issue link, where is stated this behavior as expected, since is part of the improved storage security model in Q:
https://issuetracker.google.com/issues/130965914
For your specific problem, a solution could be as next:
First query for a cursor to obtain the list of the BUCKET_ID values where all the videos are located. In the selection you can filter to target only video files by using MediaStore.Files.FileColumns.MEDIA_TYPE = MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
With the retrieved cursor, iterate all the BUCKET_ID values to perform individual queries per bucket and retrieve the video records, from which you can resolve the count. While iterating keep track of each BUCKET_ID and skip any already queried. And don't forget to also perform the same MEDIA_TYPE filter selection, to avoid querying none-video files that may reside in the same bucket.
Try the next snippet based in your question code, I haven't test it but you may get an idea about how to proceed:
public static class MediaQuery
{
#NonNull
public static HashMap<String, ModelVideoFolder> get(#NonNull final Context context)
{
final HashMap<String, ModelVideoFolder> output = new HashMap<>();
final Uri contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
final String[] projection = {MediaStore.Video.Media.BUCKET_DISPLAY_NAME,
MediaStore.Video.Media.BUCKET_ID};
try (final Cursor cursor = context.getContentResolver().query(contentUri,
projection, null, null, null))
{
if ((cursor != null) && (cursor.moveToFirst() == true))
{
final int columnBucketName = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_DISPLAY_NAME);
final int columnBucketId = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_ID);
do
{
final String bucketName = cursor.getString(columnBucketName);
final String bucketId = cursor.getString(columnBucketId);
if (output.containsKey(bucketId) == false)
{
final int count = MediaQuery.getCount(context, contentUri, bucketId);
final ModelVideoFolder item = new ModelVideoFolder(
bucketName, bucketId, null, null, count);
output.put(bucketId, item);
}
} while (cursor.moveToNext());
}
}
return output;
}
private static int getCount(#NonNull final Context context, #NonNull final Uri contentUri,
#NonNull final String bucketId)
{
try (final Cursor cursor = context.getContentResolver().query(contentUri,
null, MediaStore.Video.Media.BUCKET_ID + "=?", new String[]{bucketId}, null))
{
return ((cursor == null) || (cursor.moveToFirst() == false)) ? 0 : cursor.getCount();
}
}
}
The DISTINCT keyword actually belongs to the SELECT statement, not to a column. For example SELECT DISTINCT Country, Name FROM CountriesTable. Therefore adding DISTINCT to a column projection is a hack which randomly worked in the previous Android versions and probably stopped working in Android 10 due to some changes. Since the ContentResolver doesn't allow raw queries, you just have to filter unique folders inside your code, e. g. by using a HashSet.
I was facing the same problem. DISTINCT keyword doesn't work in Android 10, use hashset for distinct.
I'm trying to display the album names and album artwork from the songs on my device.
I tried using a comparator to sort the album names, but when i sort the album names the artwork doesn't match the album name.
How can i link the albumid with the matching artwork?
I use picasso to display the album art and.
I get the album art from MediaStore.Audio.Media.ALBUM_ID;
Then i tried using a hashmap to link albumid with album name.
But im getting this error;
E/Couldn't execute task: java.lang.NullPointerException: Attempt to invoke interface method 'boolean android.database.Cursor.moveToFirst()' on a null object reference
// Creating the map "Album IDs" -> "Album Names"
albumIdToAlbumNameMap = new HashMap<>();
//This is what we'll ask of the albums
String[] albumColumns = {
SONG_ALBUMID,
SONG_ALBUM,
};
// Querying the album database
cursor = resolver.query(musicUri, albumColumns, null, null, null);
// Iterating through the results and filling the map.
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext())
albumIdToAlbumNameMap.put(cursor.getString(0), cursor.getString(1));
cursor.close();
// Map Song IDs to Album IDs
songIdToAlbumIdMap = new HashMap<>();
// For each album, we'll query the databases
for (String albumID : albumIdToAlbumNameMap.keySet()) {
Uri uri = MediaStore.Audio.Albums.getContentUri(albumID);
cursor = resolver.query(uri, new String[] { SONG_ID }, null, null, null);
// Iterating through the results, populating the map
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
long currentSongID = cursor.getLong(cursor.getColumnIndex(SONG_ID));
songIdToAlbumIdMap.put(Long.toString(currentSongID), albumID);
}
cursor.close();
}
final String musicsOnly = MediaStore.Audio.Media.IS_MUSIC + "=1";
// Actually querying the system
cursor = resolver.query(musicUri, columns, musicsOnly, null, null);
if (cursor != null && cursor.moveToFirst())
{
// NOTE: I tried to use MediaMetadataRetriever, but it was too slow.
// Even with 10 songs, it took like 13 seconds,
do {
// Creating a song from the values on the row
QuerySongs song = new QuerySongs(cursor.getInt(cursor.getColumnIndex(SONG_ID)),
cursor.getString(cursor.getColumnIndex(SONG_FILEPATH)));
song.setTitle (cursor.getString(cursor.getColumnIndex(SONG_TITLE)));
song.setArtist (cursor.getString(cursor.getColumnIndex(SONG_ARTIST)));
song.setAlbumID (cursor.getInt(cursor.getColumnIndexOrThrow(SONG_ALBUMID)));
// Using the previously created genre maps
// to fill the current song genre.
String currentGenreID = songIdToGenreIdMap.get(Long.toString(song.getId()));
String currentGenreName = genreIdToGenreNameMap.get(currentGenreID);
String currentAlbumID = songIdToAlbumIdMap.get(Long.toString(song.getId()));
String currentAlbumName = albumIdToAlbumNameMap.get(currentAlbumID);
song.setGenre(currentGenreName);
song.setAlbum(currentAlbumName);
// Adding the song to the global list
songs.add(song);
}
while (cursor.moveToNext());
}
else
{
// What do I do if I can't find any songs?
}
cursor.close();
Here does the error occur
// Iterating through the results, populating the map
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
long currentSongID = cursor.getLong(cursor.getColumnIndex(SONG_ID));
songIdToAlbumIdMap.put(Long.toString(currentSongID), albumID);
}
Seems like you need to fix your query input to avoid null in cursor.
I have some data in SQLite database, which I would like to show in a List based on small logic.
I have three fields in my table,
first field : id
second field : title
third field : type
where Type can be : Monthly or Yearly
Here is my code:
// Generate real data for each item
public List<ReminderItem> generateData(int count) {
ArrayList<ReminderItem> items = new ArrayList<>();
// Get all reminders from the database
List<Reminder> reminders = rb.getAllReminders();
// Initialize lists
List<String> Titles = new ArrayList<>();
List<String> Type = new ArrayList<>();
List<Integer> IDList= new ArrayList<>();
// Add details of all reminders in their respective lists
for (Reminder r : reminders) {
Titles.add(r.getTitle());
Type.add(r.getType());
IDList.add(r.getID());
}
return items;
}
This is the situation, where I need help from you guys, As you can see still, I am showing each and every record from database to list, no matter from which Type it belongs.
Now, I just want to show records in a List, which belongs to Type "Monthly" only
So exactly what I have to do ? How can I show records in a List for the Type of "Monthly" only
I have a String variable namely, strType = "Monthly";
You have two option to do this.
First is directly get only those record from database which reminder type is "Monthly" by execute below sql statement.
SELECT id,title,type FROM table_name WHERE type = "Monthly"
Second is check reminder type is equal to "Monthly" when you add data into List.
for (Reminder r : reminders) {
String reminderType = r.getType();
if(reminderType.equalsIgnoreCase(strType)) {
Titles.add(r.getTitle());
Type.add(reminderType);
IDList.add(r.getID());
}
}
Full Code
// Generate real data for each item
public List<ReminderItem> generateData(int count) {
String strType = "Monthly";
ArrayList<ReminderItem> items = new ArrayList<>();
// Get all reminders from the database
List<Reminder> reminders = rb.getAllReminders();
// Initialize lists
List<String> Titles = new ArrayList<>();
List<String> Type = new ArrayList<>();
List<Integer> IDList= new ArrayList<>();
// Add details of all reminders which type="Monthly" in their respective lists
for (Reminder r : reminders) {
String reminderType = r.getType();
if(reminderType.equalsIgnoreCase(strType)) {
Titles.add(r.getTitle());
Type.add(reminderType);
IDList.add(r.getID());
}
}
return items;
}
When you select from your sqlite db you can add filter there
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_A + " WHERE " +
COLUMN_TYPE + "=?", new String[]{ arg0 }); // arg0 = "Monthly"
This will increase your performance instead of filtering List after load
I have a problem with my SQLiteOpenHelper class.
I have a database with printer manufacturers and details of any kind of printer.
I try to get all manufacturer from my database with this code and returning them in a arraylist.
// Read database for All printer manufacturers and return a list
public ArrayList<String> getPrManufacturer(){
ArrayList<String> manufacturerList = new ArrayList<String>();
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(CoDeskContract.Printer.TABLE_NAME,
printerManuProjection, null, null, null, null, null,null);
// If cursor is not null and manufacturer List
// does not contains Manufacturer in the list then add it!
if ((cursor != null) && (cursor.getCount()>0)){
cursor.moveToFirst();
do {
String cursorManufacturer = cursor.getString(0);
//Checking for manufacturer in the list
for(String manufacturerInList:manufacturerList){
if (!manufacturerInList.equals(cursorManufacturer))
manufacturerList.add(cursorManufacturer);
}
}while(cursor.moveToNext());
}
// Return list of manufacturers from database
return manufacturerList;
}
I want every manufacturer to be once in a list.
Somehow i cant to get it to work.
Im still a newbie.
Thanks for any Help.
You can also use the distinct keyword in SQLite (http://www.sqlite.org/lang_select.html). Use SQLiteDatabase.rawQuery(String query, String[] args) for that.
db.rawQuery("SELECT DISTINCT name FROM " + CoDeskContract.Printer.TABLE_NAME,null);
There are two issues:
In the beginning, when your list manufacturerInList is empty then it will not go inside for(String manufacturerInList:manufacturerList){ loop and hence it will never add any entry in the list.
Once you fix your problem 1, still it will not work as if (!manufacturerInList.equals(cursorManufacturer)) checks against each entry in the list and adds the non matching entry in the list possibly multiple times.
To fix the issue, you have two options.
Option1: Use contains as:
if (!manufacturerList.contains(cursorManufacturer)) {
manufacturerList.add(cursorManufacturer);
}
Option2: Use a matchFound boolean flag as:
String cursorManufacturer = cursor.getString(0);
boolean matchFound = false;
//Checking for manufacturer in the list
for(String manufacturerInList:manufacturerList){
if (manufacturerInList.equals(cursorManufacturer)){
matchFound = true;
break;
}
}
if(!matchFound){ // <- doesn't exit in the list
manufacturerList.add(cursorManufacturer);
}
Use ArrayList.contains(Object elem) to check if item is exist in ArrayList or not Change your code as:
// does not contains Manufacturer in the list then add it!
if ((cursor != null) && (cursor.getCount()>0)){
cursor.moveToFirst();
do {
String cursorManufacturer = cursor.getString(0);
//Checking for manufacturer in the list
if (!manufacturerList.contains(cursorManufacturer)) {
manufacturerList.add(cursorManufacturer);
} else {
System.out.println("cursorManufacturernot found");
}
}while(cursor.moveToNext());
}