Categories
discuss

Triggering a PopupMenu with buttons inside a ListView item

Types of Menu

Let me start by outlining the distinction between Contextual Menus and Popup Menus (taken from here):

  • A PopupMenu is a modal menu anchored to a View

  • A contextual menu [I’m talking specifically about floating context menus] offers actions that affect a specific item or context frame in the UI. You can provide a context menu for any view, but they are most often used for items in a ListView, GridView, or other view collections in which the user can perform direct actions on each item.

As guidance, the docs distinguish between their uses: – PopupMenu: “Providing an overflow-style menu for actions that relate to specific content (such as Gmail’s email headers…” – ContextualMenu: “For actions that affect selected content…”

I’m after the visual look and interaction of the PopupMenu.

My setup (and goal)

I have a ListView where each item has a button on the right.

--------------------
| Item 1        [ ]|
--------------------
--------------------
| Item ...      [ ]|
--------------------
--------------------
| Item n        [ ]|
--------------------

while retaining the onItemClick for each ListItem, I’d like to leverage the button to trigger a PopupMenu.

The actions in the PopupMenu are listed in my_menu.xml, so the only difference between each instance of the menu is the item that it was clicked for.

What I’ve tried

I’ve tried overriding getView() in my ListAdapter to add an OnClickListener for each button, as per this post. The result was inconsistent; not all the clicks were registering (perhaps due to recycling – I haven’t tried this fix yet) but in general the approach seemed squiffy, considering the ease of specifying a context menu for a listview.

I’ve also tried adding a contextual menu, which was dead easy, except that it’s only triggered when the list item is long-pressed.

Is the getView() method the way to go or is there something easier?

In my listview, I’ve a mini overflow button for each item, similar to each track in playlist for Play Music. They display a PopupMenu for each item there, when you click on the overflow button. Long-press does nothing.

I’d like that but not sure how? The menu page only elaborates how to enable a popup menu for individual views.

registerForContextMenu(ListView lv) is the only thing I’ve seen that I can apply to an entire ListView, but that seems to only work on long-presses of the entire list. Is there a way to hook that event to a click of a specific view in a list item (whilst still maintaining the list item click for the rest of the list item)?

I’ve tried setting an onClickListener in getView() as described here but it feels squiffy for PopupMenu, and not all clicks were registering every time (it was consistently inconsistent).

Update – with getView() overridden again

When I create the StacksCursorAdapter, I pass an instance of StackViewFragment which is stored as a field (so it can be assigned to views as the click listener).

ViewHolder is a class with public final ImageView miniOverflow

StacksCursorAdapter (extends SimpleCursorAdapter):

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View row = super.getView(position, convertView, parent);
    ViewHolder holder = (ViewHolder) row.getTag();

    if (holder == null) {
        holder = new ViewHolder(row);
        row.setTag(holder);
    }
    holder.miniOverflow.setTag(R.id.tag_stack_position, position);
    holder.miniOverflow.setOnClickListener(listener);

    return row;
}

StackViewFragment (extends ListFragment, implements View.OnClickListener):

@Override
public void onClick(View v) {
    Log.d(TAG, "mini overflow clicked");
    Log.d(TAG, "position:::::" + v.getTag(R.id.tag_stack_position));

    PopupMenu popup = new PopupMenu(getActivity(), v);
    MenuInflater inflater = popup.getMenuInflater();
    inflater.inflate(R.menu.menu_stackview_listitem, popup.getMenu());
    popup.show();
}

stack_list_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<!-- a single row item for a Stacks listview -->
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:descendantFocusability="blocksDescendants"
        >

    ...
    <other views>
    ...


    <ImageView
            android:id="@+id/moreoverflow_btn"
            android:focusable="false"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:alpha="0.4"
            android:scaleType="fitCenter"
            android:src="@drawable/ic_menu_moreoverflow_black"
            android:layout_alignParentRight="true"
            />
</RelativeLayout>

The issue at this junction is that I have to click several times occasionally to get the click to register (on the button). This is without scrolling, so it’s not related to view recycling.

Concluding (solution by eskimoapps.com marked as correct)

So there was no issue with the multiple clicks except that my touch target was not in the place I was expecting – the image asset I was using for the mini overflow is weighted to the right (such that the three dots are near the right edge of the touch target) meaning that I was just missing it half the time (facepalm).

Overriding getView() as I showed above seems to work fine, and in fact, getView() can be modified even further, by setting the OnClickListener only if it’s needed (but definitely, updating the tag needs to be done for every view, not just the newly created ones):

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View row = super.getView(position, convertView, parent);
    ViewHolder holder = (ViewHolder) row.getTag();

    if (holder == null) {
        holder = new ViewHolder(row);
        row.setTag(holder);
        holder.miniOverflow.setOnClickListener(listener);
    }
    holder.miniOverflow.setTag(R.id.tag_stack_position, position);

    return row;
}

Answer

Overriding getView is the right approach, and you’re on the right track thinking that recycling views will complicate things (if you are in fact recycling views).

Since there’s no getOnClickListener methods you’ll just have to set a new OnClickListener for the recycled views as well as the uninflated ones (if you don’t want to make a custom view that implements getOnClickListener).

Categories
discuss

How to obey Law of Demeter?

Any time I see articles about Law of Demeter the author never seems to give a solid example of how to obey this law. They all explain what it is and show an example of breaking the law but that is easy.

There is probably lots of ways to obey this law (good design and planning being one) but in very simple terms would this be a way to obey it?

Let’s say I have a class with these properties:

public class Band {

    private Singer singer;
    private Drummer drummer;
    private Guitarist guitarist;
}

I am somewhere in the program and I have an instance of this Band class and I want the guitarists name, what I usually see is something like:

guitaristName = band.getGuitarist().getName();

That one does not seem too bad as it is not going too deep in the chain but is Law of Demeter saying that maybe it should be done this way:

guitaristName = band.getGuitaristName();

and my Band class has a method:

public String getGuitaristName() {
    return guitarist.getName();
}

Is this how you are supposed to obey the law?

Thanks.

Answer

You are not applying LoD at the appropriate level: both Band and Guitarist should be considered a part of the same module and the dilemma you have shall be decided on the grounds of maximum convenience.

Your question is an example of a much wider problem, which I have frequently met in books on design patterns and similar: they try to explain wide-reaching principles, which concern the design of a complex system, on ridiculously undersized problems. The result is just reader’s confusion.

Where you’d actually see this principle in effect is something like this: you are using AsyncHttpClient, which is an abstraction built atop Netty, which is an abstraction built atop Java NIO. Now, if AsyncHttpClient‘s API forced you at some place to directly manage a Java NIO object, whose API is much more raw, and deals with concepts completely foreign to AsyncHttpClient, that would be an example of breaking LoD.

Categories
discuss

Benefits of using `Object.create` for inheritance

I’ve been trying to wrap my head around the new Object.create method which was introduced in ECMAScript 5.

Usually when I want to use inheritance I do something like this:

var Animal = function(name) { this.name = name; }
Animal.prototype.print = function() { console.log(this.name); }

var Dog = function() 
{ 
  return Animal.call(this, 'Dog'); 
}

Dog.prototype = new Animal();
Dog.prototype.bark = function() { console.log('bark'); }

I just assign a newly created Animal object to Dog’s prototype and everything works like a charm:

var dog1 = new Dog();
dog1.print(); // prints 'Dog'
dog1.bark(); // prints 'bark'
dog1.name; //prints 'Dog'

but people(without explaining) are saying that Dog.prototype = new Animal(); is not the way inheritance works and that I should use Object.create approach:

Dog.prototype = Object.create(Animal.prototype);

which also works.

What’s the benefit of using Object.create or am I missing something?

UPDATE: Some say that Dog.prototype = Animal.prototype; can also work. So now I’m totally confused

Answer

In the following I assume you are only interested in why Object.create is preferable for setting up inheritance.

To understand the benefits, lets first clarify what a “class” is made of in JavaScript. You have two parts:

  1. The constructor function. This function contains all the logic to create an instance of the “class”, i.e. instance specific code.

  2. The prototype object. This is the object the instance inherits from. It contains all methods (and other properties) that should be shared among all instances.

Inheritance establishes an is-a relation, for example, a Dog is an Animal. How is this expressed in terms of constructor function and prototype object?

Obviously a dog must have the same methods as an animal, that is the Dog prototype object must somehow incorporate the methods from the Animal prototype object. There are multiple ways to do this. You will often see this:

Dog.prototype = new Animal();

This works because an Animal instance inherits from the Animal prototype object. But it also implies that every dog inherits from one specific Animal instance. That seems to be a bit strange. Shouldn’t instance specific code only be run in the constructor function? Suddenly instance specific code and prototype methods seem to be mixed.

We don’t actually want to run Animal instance specific code at that moment, we only want all the methods from the Animal prototype object. That is what Object.create lets us do:

Dog.prototype = Object.create(Animal.prototype);

Here we are not creating a new Animal instance, we only get the prototype methods. The instance specific code is executed exactly where it should be, inside the constructor:

function Dog() { 
   Animal.call(this, 'Dog'); 
}

The biggest advantage is that Object.create will always work. Using new Animal() only works if the constructor does not expect any arguments. Imagine if the constructor looked like this:

function Animal(name) { 
    this.name = name.toLowerCase();
}

You always have to pass a string to Animal, otherwise you will get an error. What will you pass when you do Dog.prototype = new Animal(??);? It doesn’t actually matter which string you pass, as long as pass something, which hopefully shows you that this is bad design.


Some say that Dog.prototype = Animal.prototype; can also work. So now I’m totally confused

Everything that “adds” the properties from Animal.prototype to Dog.prototype will “work”. But the solutions are of different quality. In this case here you will have the problem that any method you add to Dog.prototype will also be added to Animal.prototype.

Example:

Dog.prototype.bark = function() {
    alert('bark');
};

Since Dog.prototype === Animal.prototype, all Animal instances have a method bark now, which is certainly not what you want.

Object.create (and even new Animal) add one level of indirection to the inheritance by creating a new object which inherits from Animal.prototype and that new object becomes Dog.prototype.


Inheritance in ES6

ES6 introduces a new syntax to create constructor functions and prototype methods, which looks like this:

class Dog extends Animal {

  bark() {
    alert('bark');
  }

}

This is more convenient than what I explained above, but as it turns out, extends also uses an internal equivalent to Object.create to setup inheritance. See steps 2 and 3 in the ES6 draft.
Which means that using Object.create(SuperClass.prototype) is the “more correct” approach in ES5.

Categories
discuss

Loop with delay period

I have a TextView and I want each second to highlight another letter in the word.

For example: h e l l o – h e l l o – h e l l o – h e l l o – h e l l o

What I have done:

int i = 0;
String text;
Handler handler = new Handler();

public void spanText(String txt) {
    text = txt;
    for(int i=0; i<text.length(); i++) {
        handler.post(runnable);
    }

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        Spannable spannable = Spannable.Factory.getInstance().newSpannable(text);
        StyleSpan style = new StyleSpan(Typeface.BOLD);
        spannable.setSpan(style, i, i+1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        bestResult.setText(spannable, BufferType.SPANNABLE);
        i++;
        if(i < text.length())
            handler.postDelayed(runnable, 5000);
    }
};

Unfortunately it doesn’t work and I can see only the last letter highlighted.

Thank you in advance.

Answer

int i = 0;
String text;
Handler handler = new Handler();

public void spanText(String txt) {
    text = txt;
    handler.post(runnable);

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        Spannable spannable = Spannable.Factory.getInstance().newSpannable(text);
        StyleSpan style = new StyleSpan(Typeface.BOLD);
        spannable.setSpan(style, i, i+1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        bestResult.setText(spannable, BufferType.SPANNABLE);
        i++;
        if(i < text.length())
            handler.postDelayed(runnable, 5000);
    }
};
Categories
discuss

Android music player not reindexing after song addition

I’ve been trying to create a simple MP3 downloader for Android. All it does is connect to the URL, downloads the track and guesses the ID3 information based on the name. The problem with my application is that once a song is downloaded, it does not instantly show up in the Google Music app, nor in the default music player application. I even tried some other ones without luck, they all do not reindex it.

The only thing I noticed was that when I copied/moved the song from the Music folder on my SD card to the root of the SD card, it reindexed the entire library properly. This gave me the idea that it might not be alerting any file listeners.

My question is: how would I be able to save a song to the Music folder, and having the music players update their indices? Thank you in advance.

Answer

You need to notify the MediaScanner that a new file has been added and should be indexed so it shows up in the MediaStore.

The simplest way is to send a broadcast once the file has been downloaded:

Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
sendBroadcast(intent);

http://developer.android.com/reference/android/content/Intent.html#ACTION_MEDIA_SCANNER_SCAN_FILE

The assumes the “other music players” use the MediaStore, but it’s a good bet that they do.

Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..