Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add categories to favourites #510

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

vivekscl
Copy link
Contributor

Currently, I've tried to add the tags to the database but when I try to run the app and click on the star button on a book, I get this error (tried to fit the entire error message, but it seems clicking it gives a better view):
deepinscreenshot_select-area_20180316140324

I did some searching and it seems I need to install and uninstall the app in the emulator to rebuild the SQL database so that it can support the new column that was added. But I'm unsure of how to go on about doing this because I tried to uninstall it but I'm not sure how to install the debug version of the app.

On a side note, is this good implementation for the tags in the database or does it need more improvement? 😄

@johan12345
Copy link
Collaborator

To create a new column in the database, you also need to increase the DATABASE_VERSION and create migration code such as

if (oldVersion < 7) {
    // Add column for tags
    db.execSQL("alter table " + STAR_TABLE + " add column tags text");
}

While it is possible to store the tags as JSON in a text field, it makes it difficult to filter items by a tag using database queries (instead, one needs to load all items into memory and then filter by tags). The right way to implement something like this in a relational database such as SQLite would be a many-to-many relationship (see also here) using two new database tables:

  • A tags table with just two columns - a tag ID id integer primary key autoincrement and a tag name tag text
  • A junction table that connects favorite items with tags (e.g. starred_tags) with two columns - the tag ID tag integer references tags (id) and the item id item integer references starred (id).

When creating the second table, you can add , primary key (tag, item) after the column specifications so that you don't need an extra ID for the tag <-> item assignments and so that the database ensures that one tag cannot be assigned twice on the same item.

The main table starred then does not need a new tags column - the starred_tags table already includes the necessary references. The creation of the two tables would need to be executed both in the StarDatabase.onCreate function as well as in onUpgrade when oldVersion < 7.

The benefit of this is a better database structure and better performance with many items and tags because we don't need to load all items, parse their JSON in the tags column and then filter by these tags.

@vivekscl
Copy link
Contributor Author

The right way to implement something like this in a relational database such as SQLite would be a many-to-many relationship (see also here) using two new database tables

Oh, I see. I'll brush up on my SQL and work on this. I'll post a progress report soon once I'm done with the database part. On a side note, two new databases have to be created, so that means I have to create two new classes that extend the SQLiteOpenHelper right?

@johan12345
Copy link
Collaborator

On a side note, two new databases have to be created, so that means I have to create two new classes that extend the SQLiteOpenHelper right?

Not necessarily - we only need two new tables in the database, not a whole new database. E.g. the AccountDatabase also has multiple tables.

@vivekscl
Copy link
Contributor Author

Not necessarily - we only need two new tables in the database, not a whole new database

Oh, in that case, we just need to create two new tables in StarDatabase since the tags are related only to starred items right?

@johan12345
Copy link
Collaborator

Yes :)

@vivekscl
Copy link
Contributor Author

Progress update:
Managed to add the databases for tags and the add and remove tags functions for StarDataSource. Not sure if creating a database instead of using the context in StarDataSource was appropriate, but it seemed more clean and simple.

To-do:
Add the addition and removal of tags functionality to StarredFragment
Add filter function
Add the UI

@johan12345
Copy link
Collaborator

Managed to add the databases for tags and the add and remove tags functions for StarDataSource. Not sure if creating a database instead of using the context in StarDataSource was appropriate, but it seemed more clean and simple.

Yes, I think this makes sense. The context was used because the queries in the starred table are further encapsulated in the StarContentProvider, but that is not really necessary (ContentProviders are quite complicated to implement and only really beneficial when they are used to share data with other apps).

@vivekscl
Copy link
Contributor Author

vivekscl commented Mar 21, 2018

So I'm attempting to implement the add tags functionality but there's error as seen below:
deepinscreenshot_select-area_20180322000317

Apparently, the UNIQUE constraint has been violated, so it must be the fact that a duplicate tag is being added but I'm not sure how this is occurring. For now, my implementation is such that each starred item holds a single tag.

Here's the current UI:
deepinscreenshot_select-area_20180322000010

The UI is quite lacking, but for now, I'm trying to get the functionality right. This is how the error occurs:

  1. Click on the add tag button (the plus sign)
  2. Then the error appears but the app doesn't crash
  3. The AlertDialog box appears waiting for the tag text to be entered
  4. After entering the text and clicking Ok, the box disappears but nothing happens

Also, I'm not sure why the TextView for the tag (which is below the image for MediaType), has these random values even though the text should be empty (since the tag value is supposed to be null at first)

@johan12345
Copy link
Collaborator

Hmm, I think the reason is that you are setting the tag here instead of in the click listener specified in setPositiveButton here. When addTag is executed, tagName will still be empty.

@vivekscl
Copy link
Contributor Author

Hmm, I think the reason is that you are setting the tag here instead of in the click listener specified in setPositiveButton here. When addTag is executed, tagName will still be empty.

Now it works! I mean, the text seems to be added only after typing it in the text box as seen from the logs. But the text doesn't appear. I'll try to work it out in the meantime.

On a side note, I managed to remove the random text for the tag. Apparently, its because I set the tag to a value first before loading the tag view here.

@vivekscl
Copy link
Contributor Author

Alright, I've managed to work it out and get the tag added! Here's a gif to illustrate it:
videotogif_2018 03 22_18 13 33

I'll make this into an Arraylist of Tags instead of a single instance and move on to the removal of tags. 👍 .

@johan12345
Copy link
Collaborator

Great! 😃

@vivekscl
Copy link
Contributor Author

vivekscl commented Mar 25, 2018

Progress update

Completed

  • Adding of tags using a List
  • Removing of tags using checkboxes for UI. However, when clicking the X button after deleting some tags, the deleted tags remain there as valid options unless the page is refreshed (eg. going to the accounts section and coming back) or a tag is added causing the list to refresh. If they remain there as options, clicking on them again would lead the app to crash, so there is a need to solve this but I'm not sure how. I tried to modify the tags list after the removal but it needs to be final since it's inside an anonymous class.
    Here's a GIF for illustration:

videotogif_2018 03 25_18 07 51

To-do

  • Filter function
  • Upgrading UI
  • Adding of toasts

Doubts / Errors

  • Not sure how to go about handling the SQLiteConstraintException thrown from addTag in StarDataSource. More specifically, not sure how to create a UI to handle it at the moment.
  • After deletion of the starred item itself, if the deletion is undone, the item returns but without the tags. Is this behavior appropriate or does it need to return with the tags?

@johan12345
Copy link
Collaborator

One suggestion regarding the UI: Maybe it would make more sense to just have one button on every item (e.g. with this icon ) that shows a dialog with the list of currently assigned tags which can be removed with a "delete" button. New tags could then be added with an AutoCompleteTextView below. Tags that are already on the item should then not be offered as autocomplete options and just not be added to the list above if the user tries to add them anyway (with an appropriate Toast message).

This probably means that a custom layout would need to be created for the AlertDialog.

Regarding the logic in the addTag and removeTag functions:

  • in addTag, first try to find an existing tag with that name in the tags table. If there is one, use that instead of creating a new one with the same name - this also means that one could add a unique constraint on the tag column of the tags table
  • in removeTag, only delete the record from the tags table if no other entry in the star_tags table refers to it, i.e. no other item has that same tag assigned.

This way, the filtering and autocompleting will be easier to implement.

@vivekscl
Copy link
Contributor Author

vivekscl commented Mar 30, 2018

Progress update

Completed

  • Improved the functionality of the adding and removal of tags
  • Improve UI
    Here's a GIF for illustration:

videotogif_2018 03 31_01 48 29

To-do

  • Complete filter function
  • Improve UI further

Updates

  • I tried to use the AutoCompleteTextView but it kind of resulted in the app not responding because of the database querying. I tried to load all the tag names from every item except the current one from the database for the purpose of the auto-complete drop-down list. But this takes too long and causes the app to crash. If you would like to take a look, the code I came up with for the AutoCompleteTextView can be seen from the commented code.

    As a result, I moved on to using just a simple EditText instead. There is still a need to improve the UI of the EditText and the Add button though. On a side note, is there a need for the Back button on the AlertDialog since the user can just exit by clicking the back button on the phone itself?

  • Is there a way to move the Add button to the side of the EditText? And is it better to have the EditText as a textbox instead of a straight line?

PS: Sorry for taking so long on this PR! 😅

@johan12345
Copy link
Collaborator

Very nice!

I tried to use the AutoCompleteTextView but it kind of resulted in the app not responding because of the database querying. I tried to load all the tag names from every item except the current one from the database for the purpose of the auto-complete drop-down list. But this takes too long and causes the app to crash. If you would like to take a look, the code I came up with for the AutoCompleteTextView can be seen from the commented code.

Okay, strange - I'm quite busy at the moment and will be travelling for two weeks starting April 8, so it might take a while until I can take a look at this. But no problem, you can continue with the EditText for now and we replace it with a AutoCompleteTextView later.

On a side note, is there a need for the Back button on the AlertDialog since the user can just exit by clicking the back button on the phone itself?

I think I would still keep the Back button there, it might be confusing for the user if the dialog has no buttons.

Is there a way to move the Add button to the side of the EditText?

Yes, you can use another horizontal LinearLayout to place the button next to the EditText or even use a RelativeLayout like this (and an ImageButton with a plus symbol) to put the button on the right side of the EditText.

And is it better to have the EditText as a textbox instead of a straight line?

Sorry, I don't understand what you mean with this?

PS: Sorry for taking so long on this PR! 😅

Absolutely no problem - this idea has been sitting around for a few years now, it is not urgent in any way 😉

One more thing that I noticed: this should be changed to android:layout_toLeftOf="@id/ivTagMenu so that the text does not continue behind the tag menu button

@vivekscl
Copy link
Contributor Author

vivekscl commented Mar 31, 2018

Sorry, I don't understand what you mean with this?

Oh, I meant if it's better if it looks like this instead of the current style of the EditText?
deepinscreenshot_select-area_20180401002407

@johan12345
Copy link
Collaborator

Ah - I think it's good in the current style - that's the default in Material Design.

@vivekscl
Copy link
Contributor Author

On a side note, I've been quite busy with my final exams. So once I'm done with that, I'll continue work on this! Sorry for the delay! 😅

@johan12345
Copy link
Collaborator

No problem - hope the exams go well!

Implement add tags functionality
The following were done as well:
- Improve add tag functionality to use a list of tags of instead of
single tags
- Add comments to code
- Implement Filterable interface for ItemListAdapter
- Add search bar for fragment_starred.xml
- Update tag_menu.xml to place the add button to the 
right of the edit text
@vivekscl
Copy link
Contributor Author

Progress Update

So I implemented the filter function and got it to filter by the tags. I then add the corresponding titles of the filtered items into a list and notify that the data has changed. The list is updated correctly to reflect the titles of the books like so:
deepinscreenshot_select-area_20180511200342

But I'm not sure as to how I could update the UI to reflect this. It shows a list of all the starred items instead of just showing eBoy : Hello and Hello:
deepinscreenshot_select-area_20180511200628

@johan12345
Copy link
Collaborator

Great!

I have just looked at the AutoCompleteTextView problem and fixed it with the commit 17e0463 (the error was just related to the position cursor.moveToNext(); which lead to an endless loop).

Regarding the Filter UI: I think a search where you only need to input part of the tag name - it would be enough if you first "search for the tag" in the search field, select it, and then the list is filtered by that tag. I think a nice UI solution for this would be this library:
https://github.com/hootsuite/nachos
(you start to input the tag name, it gives you suggestions like an AutocompleteTextView, you can select the tag, and then it appears as a "chip" - that way you can also filter by multiple tags). A different way would be another button in the action bar that shows a dialog where one can select the tag(s) that you want to see. Before you implement one of these UI solutions let's first ask @raphaelm what he thinks would be the most appropriate.

I don't know why the getFilter() function you implemented doesn't work, but maybe the easier way would be just to reload the list (like currently in the accountSelected function) and adjust the onCreateLoader function so that it does not only filter by library, but also by tag.

@johan12345
Copy link
Collaborator

I discussed with @raphaelm - probably for the filtering by tag, the best UI would be to add another dialog where one can select one (or multiple) from the list of all tags. The "nachos" library would be nice for the dialog where the tags can be added to the individual items, but the current solution is fine, too.

@vivekscl
Copy link
Contributor Author

I have just looked at the AutoCompleteTextView problem and fixed it with the commit 17e0463 (the error was just related to the position cursor.moveToNext(); which lead to an endless loop).

Thanks for helping me out with this!

the easier way would be just to reload the list (like currently in the accountSelected function) and adjust the onCreateLoader function so that it does not only filter by library, but also by tag.

For this, I'm rather confused as to how I could filter by library and tag as well. Since these two details are from different tables, it seems like I would have to do a raw SQL query in order to join the two tables. However, the CursorLoader constructor doesn't take in a JOIN query right? It seems like it specifically does a SELECT query only. I've tried to google how to do this and from this link it seems like I would have to create a new URI?

For your convenience, here is the SO post:

The Uri does not point to any table. It points to whatever you feel like pointing it to.

Let's pretend that your two tables are Customer and Order. One customer may 
have many orders. You want to execute a query to get all outstanding orders... 
but you want to join in some customer-related columns that you will need, 
such as the customer's name.

Let's further pretend that you already have 
content://your.authority.goes.here/customer and 
content://your.authority.goes.here/order defined to purely query those tables.

You have two choices:

1. Add the join of the customer's display name on your /order Uri. Having 
another available column probably will not break any existing consumers of the 
provider (though testing is always a good idea). This is what ContactsContract 
does -- it joins in some base columns, like the contact's name, on pretty much all 
queries of all tables.

2. Create content://your.authority.goes.here/orderWithCust that does the same basic 
query as /order does, but contains your join. In this case, you could have insert(), 
update(), and delete() throw some sort of RuntimeException, to remind you that you 
should not be modifying data using /orderWithCust as a Uri.

So far, I have got the getFilter() function to work and restart the loader in order to update the UI by modifying onCreateLoader() to filter by the library and filtered titles.:
deepinscreenshot_select-area_20180527195755

However, this implementation is rather clunky and results in slow/no update of the favourites list 😅 . So I guess it would be better if I'm able to filter using an INNER JOIN query.

As for the nachos library, I'll look into it as soons as I've figured out this filter logic!

@johan12345
Copy link
Collaborator

For this, I'm rather confused as to how I could filter by library and tag as well. Since these two details are from different tables, it seems like I would have to do a raw SQL query in order to join the two tables. However, the CursorLoader constructor doesn't take in a JOIN query right? It seems like it specifically does a SELECT query only. I've tried to google how to do this and from this link it seems like I would have to create a new URI?

Sorry, I thought this would be easier, I forgot that a join is needed.

I haven't had much time to look into this, but I think the two options are either to add this join as a raw SQL query in the StarContentProvider or drop the CursorLoader altogether and use a normal ArrayAdapter instead.

@vivekscl
Copy link
Contributor Author

vivekscl commented Jul 7, 2018

Progress Update

Sorry for the really late update! I was really busy with my internship and just got some free time 😅.

I've formed the raw query now but my question is how do I place it in the StarContentProvider.query() method? I've been trying to understand how it works but I can't wrap my head around the meaning of STAR_DIR and STAR_ITEM. Also, I'm not sure if I can simply just add a another URI case in StarContentProvide#L165 so that it can use the join query. Here's my join query for reference:

image

@johan12345
Copy link
Collaborator

Nice! Currently the StarContentProvider supports two types of URIs:

  • content://de.geeksfactory.opacclient.content/star - list of starred items
  • content://de.geeksfactory.opacclient.content/star/{id} - single starred item with ID {id}
    As the types of content that the two URIs return differ, there are two MIME types STAR_DIR (list of items) and STAR_ITEM (single item).

As seen in the StackOverflow post that you posted previously, there are two possibilities: either you use your new query with the JOINs every time when the STAR_DIR URI is queried (https://github.com/opacapp/opacclient/blob/master/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarContentProvider.java#L158) or add a new kind of URL like content://de.geeksfactory.opacclient.content/star/withTags (together with a new MIME type STAR_DIR_WITH_TAGS) that then uses the new JOIN query. In both cases, I think you don't need to have the WHERE clause in the StarContentProvider, because that can be passed when constructing the CursorLoader in onCreateLoader of the StarredFragment.

Also, if we don't use a free text input for filtering by tags, but show the list of tags and let the user select from that, you don't need the second JOIN, because then you would just query the database for the selected tag's ID instead of its name.

- Remove Filterable interface for ItemListAdapter
- Remove search bar for fragment_starred.xml
- Add an SQL join raw query to filter tags using the tag IDs
- Add a dialog for user to select tags to filter with
@vivekscl
Copy link
Contributor Author

vivekscl commented Aug 7, 2018

Progress Update

I have just completed the functionality for the filter using user selection instead of free text input and I've also removed the second inner join accordingly. Here's the result:

videotogif_2018 08 07_15 00 17

I'm not sure if UI is acceptable so I'm open to suggestions!

On the other hand, I've imported the "nachos" library and I'll look up on how to add it to the Add Tag dialog. One question on this though, I would just have to replace the AutoCompleteTextView with the NachoTextView right?

<item
android:id="@+id/action_filter_by_tags"
android:icon="@drawable/ic_search_24dp"
app:showAsAction="never"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could change this to app:showAsAction="ifRoom" so that it is shown as an action button. The Icon could be changed to the filter icon.

@johan12345
Copy link
Collaborator

I haven't yet tested it myself and looked at the code in detail, but the UI looks very nice in the video you sent!

I added one remark below about the action button.

@vivekscl
Copy link
Contributor Author

vivekscl commented Aug 7, 2018

I haven't yet tested it myself and looked at the code in detail, but the UI looks very nice in the video you sent!

Thanks! 😄

I added one remark below about the action button.

I've fixed this and made sure the icon was white so as to be consistent with the menu.

On the other hand, I've imported the "nachos" library and I'll look up on how to add it to the Add Tag dialog. One question on this though, I would just have to replace the AutoCompleteTextView with the NachoTextView right?

On a side note, I would like to clarify this part! Thanks!

@johan12345
Copy link
Collaborator

On a side note, I would like to clarify this part! Thanks!

Ah, sorry, I overlooked this. Yes, so the AutoCompleteTextView would be replaced with the NachoTextView, but also the list above would not be necessary, because the NachoTextView can display both the list of currently assigned tags as well as using it to add new ones - that's its advantage.

@vivekscl
Copy link
Contributor Author

vivekscl commented Aug 7, 2018

Yes, so the AutoCompleteTextView would be replaced with the NachoTextView, but also the list above would not be necessary, because the NachoTextView can display both the list of currently assigned tags as well as using it to add new ones - that's its advantage.

Thanks for clarifying that with me!

Description of UI

Currently I've been experimenting with the UI and I'm wondering if this implementation would be suitable.

  1. Whenever the user deletes a chip by clicking on the backspace, that tag is automatically deleted from that favourite item (should a toast msg be shown here?).

  2. Whenever a user creates a chip, that tag is automatically added to the list of tags for that favourite item (again, should a toast msg be shown here?)

This is how it would look like:
videotogif_2018 08 07_20 41 02

Implementation

To implement this, I would just need to compare the list of chip values before and after whenever a new chip is created or removed.

Pros and Cons

I believe that this is a more simple and user friendly option. Also, we can remove alot of code from the previous implementation which requires the add button. However, one issue is that this way of adding and removing tags is a bit unforgiving, since the user can mistakenly remove a tag by clicking the backspace accidently.

Please let me know if you have suggestions! Thanks!

@johan12345
Copy link
Collaborator

Thanks! Yes, the way you described it sounds like I imagined. One thing that could be added is an "x" icon on the right side of the chips (as seen here) so that they can also be deleted with that instead of just using backspace.

Also, similar to the AutocompleteTextView, the chip view should also offer autocomplete when adding a new tag.

However, one issue is that this way of adding and removing tags is a bit unforgiving, since the user can mistakenly remove a tag by clicking the backspace accidently.

Yeah, I see that issue. Normally I would have added a Snackbar with an "undo" button to allow the user to quickly undo the deletion of the tag. However, that probably looks odd inside a dialog, so we would probably have to think about another solution. Or we just leave it, as it should be quite easy to re-add an accidentally deleted tag using the autocomplete function.

@vivekscl
Copy link
Contributor Author

vivekscl commented Aug 11, 2018

Thanks! Yes, the way you described it sounds like I imagined. One thing that could be added is an "x" icon on the right side of the chips (as seen here) so that they can also be deleted with that instead of just using backspace.

I've been trying to find a way to add a ClickListener to the close icon so as to delete the tag from the item and then the database, but it seems that it can't be done with the Nachos library. This is because the chip icon is added programmatically and not through the xml file as shown below:
deepinscreenshot_select-area_20180811125841

Should another library be used instead? Like the google material library as shown in the link you gave above or this MaterialChipView library.

This is how it looks like for now, if implemented using the Nachos library (it doesn't look like it fits 😅 ):
imag0626

@johan12345
Copy link
Collaborator

Hm, yeah, strange, it seems that the nachos library has no way of setting a click listener for the icon, just for the whole chip :/

In the Google Material Design library, the chip component was only added recently, maybe we can use that, there's just not much documentation I could find about how to use it inside an EditText input. Maybe this helps? https://stackoverflow.com/a/51610997

For using the material components library, the compileSdk version of the app first needs to be changed to Android P (28) and the currently used support library versions need to be changed to the new AndroidX libraries (see also here). Probably there are also some adjustments needed to the multiline-collapsingtoolbar library that we use. I can try to do this soon, but I probably won't have time for it next week.

@vivekscl
Copy link
Contributor Author

For using the material components library, the compileSdk version of the app first needs to be changed to Android P (28) and the currently used support library versions need to be changed to the new AndroidX libraries (see also here). Probably there are also some adjustments needed to the multiline-collapsingtoolbar library that we use. I can try to do this soon, but I probably won't have time for it next week.

Oh I see. Seems like it would take quite a bit of time to implement that as well.

In the Google Material Design library, the chip component was only added recently, maybe we can use that, there's just not much documentation I could find about how to use it inside an EditText input. Maybe this helps? https://stackoverflow.com/a/51610997

In the meantime, I'll get started with this and maybe try to look for other solutions if possible! 😄

@johan12345
Copy link
Collaborator

johan12345 commented Aug 16, 2018

@vivekscl I started with the migration to Android P on the pie branch (see #529). While I haven't yet fully tested that everything works, the app at least compiles and starts fine. I think you can now start to git rebase your PR onto the pie branch and update to Android Studio 3.2 Beta. Then it should be possible to use the new Chips from the Material Components library (which is already included in the build.gradle file there).

@vivekscl
Copy link
Contributor Author

I think you can now start to git rebase your PR onto the pie branch and update to Android Studio 3.2 Beta.

Alright I understand! I'll get started on it soon!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants