NewFiveFour | Blog | Portfolio


Swift 3 and iOS: Save a file

Let’s say you already have a Data object filled with PDF data, or whatever.

We first get the url for our documents directory (in our user’s home domain). Then we append our filename to that.

Finally we write our data above to this new url atomically, marking it with try since it may throw an exception.

var docURL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last
docURL = docURL?.appendingPathComponent("sample1.pdf")
try OURDATA.write(to: docURL!, options: .atomicWrite)
swift ios

Swift 3 and iOS: Read a value from a plist

Create a Something.plist file in your main project. Add a String value with the key SomethingYeah in it.

Then, from your main bunle, get a path for the resource Something with the type plist.

Then get a dictionary from that path. And then use that to get the value we inserted above.

if let path = Bundle.main.path(forResource: "Something", ofType: "plist") {
    let dictRoot = NSDictionary(contentsOfFile: path)
    if let dict = dictRoot {
        debugPrint(dict["SomethingYeah"] as! String)
    }
}
swift ios

Bulk renaming files in Android Asset Studio download

If you use Roman Nurik’s wonderful Android Asset Studio, you’ll get a downloaded zip of differently sized icons.

You may want to rename these files from ic_launcher.png to something else - otherwise it may clash with an existing file of that name.

You can use xargs to do this. Move to the directory of the downloaded file, in the res directory and run this:

ls | xargs -I {} mv {}ic_launcher.png {}YOUR_NEW_FILENAME.png

We list each mipmap-xxxx directory, and rename all the ic_launcher.png files within to our new filename.

android unix-xargs

Android: RecyclerView with animated expandable sections

In previous posts, we’ve created a RecyclerView with section headings and a click listener. Look for posts under the android-recyclerview tag.

We continue to use our data object, SectionOrRow, that defines if the data is a section or a row. But we will change three lines in the adapter. These changes will define our list size and the list item by taking into account the hidden positions.

In getItemCount() we will get the size from SectionOrRow.getSizeExcludingHidden(mData). And in getItemViewType() and onBindViewHolder() we will get the data item by calling SectionOrRow.getUnderlyingItem(mData, position).

Outside our adapter, we will now look for clicks on the section headings. If we get one, we will check if it’s expanded or not, and if it is we will call a method that hides or shows our rows:

adapter.setClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Get the position of the view
        int visiblePosition = YOUR_RECYCLER_VIEW.indexOfChild(v);
        // Get the item it represents
        StringSectionOrRow item = SectionOrRow.getUnderlyingItem(data, visiblePosition);
        // if the section is not hidden, hide the section, and if it is, show it.
        if(!item.isRow() && !item.getSectionHidden()) {
            int numberHidden = SectionOrRow.hideUntilNextSection(data, visiblePosition, true);
            adapter.notifyItemRangeRemoved(numberHidden+1, numberHidden);
        } else if(!item.isRow() && item.getSectionHidden()) {
            int numberShown = SectionOrRow.hideUntilNextSection(data, visiblePosition, false);
            adapter.notifyItemRangeInserted(numberShown+1, numberShown);
        }
    }
});

SectionOrRow.hideUntilNextSection(data, visiblePosition, true) will look at our list of data, and set all the rows until the next section as hidden. The last parameter defines if we’re going to set it to hidden or not.

adapter.notifyItemRangeInserted(visiblePosition+1, hidden) tells our recycler view to animate the insertion of our views, or to animate their delection. That is, when we set our underlying data to hidden, we want the recycler view to animate their removal.

Our old SectionOrRow class now has two new booleans: hidden and sectionCollapsed. These define if we should show or hide the row and if the section is collapsed.

The first new method in this class is the one used to size the list including all the hidden rows. Its implementation is straight forward.

public static int getSizeExcludingHidden(List<SectionOrRow> rows) {
    int size = 0;
    for (StringSectionOrRow r : rows) {
        if(!r.hidden) size++;
    }
    return size;
}

The second is used internally only. You give it the position which does not include all the hidden cells. The adapter will give you this value. It will then return the position including the hidden rows.

We loop over all the underlying items, and if such is not hidden we increment a visibleRow integer. Eventually this will match the position we’re looking for, and we return the underlying position:

private static int getUnderlyingRowPosition(List<StringSectionOrRow> rows, int visiblePosition) {
    int underlyingRow = 0, visibleRow = 0;
    for (; underlyingRow < rows.size(); underlyingRow++) {
        StringSectionOrRow stringSectionOrRow = rows.get(underlyingRow);
        if(!stringSectionOrRow.hidden && visibleRow==visiblePosition) break;
        if(!stringSectionOrRow.hidden) visibleRow++;
    }
    return underlyingRow;
}

The next method is the one where we get the item to display. We give it the position in the adapter that does not include all the hidden cells. And, via the above, it returns the underlying row for the adapter to display:

public static SectionOrRow getUnderlyingItem(List<StringSectionOrRow> rows, int visiblePosition) {
    return rows.get(getUnderlyingRowPosition(rows, visiblePosition));
}

Our final function is the one we use to set the rows to be hidden or shown. We give it the row objects the adapter is given, the position of our section header (as found in the above click listener) and then define if we want to either hide or show the rows:

public static int hideUntilNextSection(List<StringSectionOrRow> rows,
                                       int visiblePosition,
                                       boolean hide) {
    // What's he underlying row data
    int underlying = StringSectionOrRow.getUnderlyingRowPosition(rows, visiblePosition);
    SectionOrRow section = rows.get(underlying);
    // Say that's now collapsed (or fully visible)
    section.setSectionCollapsed(hide);
    underlying++; // Go to the next row
    // Let's now either show or hide those values
    // until we reach the next section
    int rowsHidden = 0;
    for (int i = underlying; i < rows.size();i++,rowsHidden++) {
        StringSectionOrRow r = rows.get(i);
        if(!r.isRow) break;
        else {
            r.setHidden(hide);
        }
    }
    return rowsHidden;
}

Now when the recycler view sizes itself, it takes into account the hidden cells, and when it display an item, it does the same.

When one its section heading is clicked, it checks if it’s already collapsed, if it is it sets all the rows under that section to visible, and the animates their insertion.

Equally, if the section is not collapsed, it sets all the rows to hidden, and animates their removal.

android android-recyclerview

Android: On item click listener for RecyclerView Adapter

RecyclerView.Adapter, unlike its ListView colleague, does not have a item click listener.

You can, however, use a normal View.OnClickListener and then use indexOfChild to get the position of the view in the recycler view.

Add the callback setter to your adapter:

public void setClickListener(View.OnClickListener callback) {
    mClickListener = callback;
}

And in your onCreateViewHolder set that:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);
    RecyclerView.ViewHoldre holder = new SomeViewHolder(v);
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mClickListener.onClick(view);
        }
    });
    return holder;
}

Now, outside the adapter, you can fetch the position like so:

YOUR_RECYCLER_ADAPTER.setClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        int pos = YOUR_REYCLER_VIEW.indexOfChild(v);
        ...
    }
});

And then pos will have the index of the view.

This is better than having the position passed through in the callback, since any callback binding may have the incorrect position, if the internal position of the views change.

android android-recyclerview

Page 3 of 76
Previous Next