In my list view for ITEM I get the title of the feed but I want to get the description, image and link in the SUBITEM in that list view. Can you help me?
This is what I have:
1) The ListView in MainActivity
ArrayAdapter<RSSItem> adapter;
adapter = new ArrayAdapter<RSSItem>(
this,
android.R.layout.simple_list_item_1,
myRssFeed.getList()
);
setListAdapter(adapter);
2) RSSItem
public class RSSItem {
private String title = null;
private String description = null;
private String link = null;
private String pubdate = null;
RSSItem(){}
void setTitle(String value) {
title = value;
}
void setDescription(String value) {
description = value;
}
void setLink(String value) {
link = value;
}
void setPubdate(String value) {
pubdate = value;
}
String getTitle() {
return title;
}
String getDescription() {
return description;
}
String getLink() {
return link;
}
String getPubdate() {
return pubdate;
}
public String toString() {
//TODO Auto-generated method stub
return title;
}
}
Just use a custom ArrayAdapter. It's super simple:
1) Define your custom ArrayAdapter. Fill in the body of getView() to create a view based on each item your pass to the adapter;
public class YourArrayAdapter<YourDataObject> extends ArrayAdapter<T> {
public YourArrayAdapter(Context context) {
super(context, 0); // Pass in 0 because we will be overriding getView()
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// getView() gets called when this item becomes visible in the ListView
// All you have to do is build a view with your data object and return it.
YourDataObject yourDataObject = getItem(position);
YourView view = new YourView(yourDataObject);
}
}
2) Pass the adapter to your ListView, and add data.
YourArrayAdapter<RSSItem> adapter = new YourArrayAdapter<RSSItem>(this);
adapter.addAll(myRssFeed.getList());
setListAdapter(adapter);
The important thing to realize here is that Adapters turn lists of data into actual UI Views when the ListView requests a view via Adapter.getView(). In this case, you are creating a custom ArrayAdapter, and so you control everything about the View that it returns. You can return a view that has its own layout, and includes the many different pieces of data that RSSItem includes, and presents them in whatever format you desire. The cool thing is that by using ListView and a custom ArrayAdapter, you never have to worry about creating or destroying these views -- that's all taken care of for you. So if the items your return from ArrayAdapter.getView() include bitmaps, you don't need to really worry too much about running out of memory.
Related
How can I create a listview that looks for more than one information in an array?
An example, suppose I have a listview of names from an array, but I still have another array that contains data like age and profession that match each name of the first array.
How could I get more than one information from an array?
Thank you so much.
What is happening when you load an ArrayList into an ArrayAdapter, and then an ArrayAdapter into a ListView, is the ArrayAdapter uses a layout file which contains a TextView. The ArrayAdapter takes the string in each ArrayList element, inflates (creates) a new View with a layout per ArrayList element, and then places the element string in each new layout's TextView.
If you want to customize each row's visual appearance (including what data appears) in your list on the screen, you can make your own custom Adapter by making a new class that extends BaseAdapter and you can make your own layout file that you will inflate in the BaseAdapter. Then find the elements in your layout and assign the data to the elements on a per row basis. Below is example code I wrote for you. I would highly suggest reading the Android documentation on ListViews and Adapters: https://developer.android.com/guide/topics/ui/layout/listview.html
https://developer.android.com/guide/topics/ui/declaring-layout.html#AdapterViews
In my activity OnCreate method (You will need to have a ListView in your layout called list_view or change the name of the ListView in my code):
ArrayList<MyDataModel> myDataModels = new ArrayList<>();
for(int i = 0; i < 50; i++) {
MyDataModel newModel = new MyDataModel("Person" + i, new Random().nextInt() % 100, "Some Profession" + i);
myDataModels.add(newModel);
}
MyListAdapter myListAdapter = new MyListAdapter(myDataModels);
ListView listView = (ListView)findViewById(R.id.list_view);
listView.setAdapter(myListAdapter);
MyDataModel.java:
public class MyDataModel {
public String mName, mProfession;
public int mAge;
public MyDataModel(String name, int age, String profession) {
mName = name;
mAge = age;
mProfession = profession;
}
}
MyListAdapter.java:
public class MyListAdapter extends BaseAdapter {
private ArrayList<MyDataModel> mMyDataModels;
public MyListAdapter(ArrayList<MyDataModel> dataModels) {
mMyDataModels = dataModels;
}
#Override
public int getCount() {
return mMyDataModels.size();
}
#Override
public MyDataModel getItem(int position) {
return mMyDataModels.get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view == null) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_data_model_item, parent, false);
}
MyDataModel model = mMyDataModels.get(position);
((TextView)view.findViewById(R.id.person_name)).setText(model.mName);
((TextView)view.findViewById(R.id.person_age)).setText(String.valueOf(model.mAge));
((TextView)view.findViewById(R.id.person_profession)).setText(model.mProfession);
return view;
}
}
in my android project,
i am getting a list of data in arraylist
ArrayList<Items> item = db.getAllMenu();
but now i want to add this data into listview,
i tried as,
ListView lv=(ListView)findViewById(R.id.list_view_inside_nav);
String[] lv_arr = {};
lv_arr = (String[]) item.toArray();
lv.setAdapter(new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1, lv_arr));
but its giving error.because i am trying to convert arratlist to string..
anyone plz help me, how to convert the arraylist to string[]
here are my some files...
items.java (getter and setter methods)
public class Items {
//private variables
String _name;
// Empty constructor
public Items(){
}
// constructor
public Items(String name){
this._name = name;
}
// getting name
public String getName(){
return this._name;
}
// setting name
public void setName(String name){
this._name = name;
}
}
and i am using this code to get data from database
public ArrayList<Items> getAllMenu() {
ArrayList<Items> passList = new ArrayList<Items>();
// Select All Query
String selectQuery = "SELECT * FROM " + CATEGOTY_TABLE_NAME;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Items menu = new Items();
menu.setName(cursor.getString(0));
// Adding category to list
passList.add(menu);
} while (cursor.moveToNext());
}
// return category list
return passList;
}
Your problem here is that you are trying to convert a List into an Array, this can't be done with a cast but methods exist.
The easiest would be to convert the list into an Array using the methods List.toArray(E[]).
ArrayList<Items> items = db.getAllMenu();
Items[] itemsArray = new Items[items.size()]; //Create the array to the correct size,
itemsArray = items.toArray(itemsArray); //Fill the array with the list data
//you could cast into Item[] directly but this is cleaner
Help to use here : Convert ArrayList<String> to String[] array
The array need to be an array of Items
Then, if you have override the toString methods of your Item class to return the String like you want.
public class Items {
...
#Override
public String toString(){
this.getName() //Just an example ;)
}
...
}
This will work like a charm. The adapter use this method to get the String to print.
EDIT :
After some research, you don't even need to create an array. ArrayAdapter accept an List so you only need to override Items.toString()
https://developer.android.com/reference/android/widget/ArrayAdapter.html
You can see here the need to override the toString
However the TextView is referenced, it will be filled with the toString() of each object in the array. You can add lists or arrays of custom objects. Override the toString() method of your objects to determine what text will be displayed for the item in the list.
And here is the constructor to use
ArrayAdapter (Context context,
int resource,
List objects)
So just create your adapter like this :
new ArrayAdapter<Items>(MainActivity.this,
android.R.layout.simple_list_item_1, item);
You have to make arraylist of object to do such thing here is a piece of my code that i use
My ArrayList
private ArrayList<Mediafileinfo> songList = new ArrayList<Mediafileinfo>();
Adding data in the arraylist object.
Mediafileinfo info = new Mediafileinfo();
info.setFile_uri(Uri.parse(audioCursor.getString(audiodata)));
info.setName(audioCursor.getString(audioTitle));
info.setDuration(audioCursor.getLong(audioduration));
info.setAlbum(audioCursor.getString(audioalbum));
info.setAlbum_Art_uri(ContentUris.withAppendedId(albumArtUri, audioCursor.getLong(audioalbumid)));
songList.add(info);
Make a class with getter and setter
public class Mediafileinfo {
private String name,album,artist;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlbum() {
return album;
}
public void setAlbum(String album) {
this.album = album;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
}
And call in your arraylist adapter like this
Mediafileinfo mediafileinfo = (Mediafileinfo) getItem(position);
TextView textView = (TextView) view.findViewById(R.id.textView);
textView.setText(mediafileinfo.getAlbum());
and rest will be the same
you can set the arraylist in your adapter like this
new CustomAdapter(this,songlist)
Hope this will help you.For more info
Wrong.
Items class cannot convert to a String class.
You need to convert each Items to String object.
Example :
Say your class Item
class Items{
public String itemName;
}
In your code change
String[] lv_arr = new String[items.size()];
for(int i=0;i<items.size();i++){
lv_arr[i]=item.get(i);
}
lv.setAdapter(new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1, lv_arr));
let listobj be a list of object you want toset for your listView and Lv be your listview
use following code:
LV.setAdapter(new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1 ,sensors));
update
if you want each item of your LisView represent a specific object you could also populate it with a custom adaptor like this :
first in your java files define new javaclass that extends BaseAdaptor
public class SensorAdaptor extends BaseAdapter{
private final Context context;
private final List<Sensor> sensors;
public SensorAdaptor(Context context , List<Sensor> sensors){
this.context = context;
this.sensors = sensors;
}
#Override
public int getCount() {
return sensors.size();
}
#Override
public Object getItem(int position) {
return sensors.get(position);
}
#Override
public long getItemId(int position) {
return sensors.get(position).getType();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.list_sensors, null);
} else {
view = convertView;
}
TextView listName = view.findViewById(R.id.txtSensorList);
listName.setText(sensors.get(position).getName());
return view;
}
}
attention in my case i want each item of listView represent a Sensor object
then in layout file in res/layout define a layout for this adaptor to use
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/**txtSensorList**"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
and then in yor activity
List<Sensor> sensors = mgr.getSensorList(Sensor.TYPE_ALL);
LV.setAdapter(new SensorAdaptor(getContext(),sensors));
attention
in my case i want to show a list of sensor Object
I'm trying to make a simple to-do list where you would long-press an item to mark it as 'done', in which case it will be greyed out and strikethrough.
I'm working on the strikethrough first and found some sample code here creating a strikethrough text in Android? . However the problem is that the setPaintFlags() method only seems to work on TextView whereas the items on my list are String. I can't cast a String to a TextView, and I found a workaround here but apparently it's highly discouraged to do it: Cast String to TextView . Also I looked up SpannableString but it doesn't seem to work for strings of varying length.
So I'm back at square one - is it at all possible to implement what I'm trying to do? Or will I have to store my list items differently instead?
Relevant code:
public class MainActivity extends ActionBarActivity {
private ArrayList<String> items;
private ArrayAdapter<String> itemsAdapter;
private ListView lvItems;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Setting what the ListView will consist of
lvItems = (ListView) findViewById(R.id.lvItems);
readItems();
itemsAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);
lvItems.setAdapter(itemsAdapter);
// Set up remove listener method call
setupListViewListener();
}
//Attaches a long click listener to the listview
private void setupListViewListener() {
lvItems.setOnItemLongClickListener(
new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> adapter,
View item, int pos, long id) {
// Trying to make the onLongClick strikethrough the text
String clickedItem = items.get(pos);
//What do I do here??
// Refresh the adapter
itemsAdapter.notifyDataSetChanged();
writeItems();
// Return true consumes the long click event (marks it handled)
return true;
}
});
}
Let's take a step back and consider your app. You want to show a list of jobs to the user. Each job has a description. And each job has two possible states: 'done' or 'not done'.
So I would like to introduce a class 'Job'
class Job
{
private String mDescription;
private boolean mDone;
public Job(String description)
{
this.mDescription = description;
this.mDone = false;
}
// ... generate the usual getters and setters here ;-)
// especially:
public boolean isDone()
{
return mIsDone;
}
}
This way your ArrayList 'items' becomes be a ArrayList< Job >. Wether a job is done or not will be stored together with its description. This is important because you want to show the current state of the job to the user by changing the look of the UI element, but you need to keep track of the job's state on the data level as well.
The UI element - the TextView - will be configured to present information about the job to the user. One piece of information is the description. The TextView will store this as a String. The other piece of information is the state (done/ not done). The TextView will (in your app) store this by setting the strike-through flag and changing its color.
Because for performance reasons a ListView uses less elements than the data list ('items') contains, you have to write a custom adapter. For brevity's sake, I'm keeping the code very simple, but it's worth the time to read up on the View Holder pattern:
Let's use a layout file 'mytextviewlayout.xml' for the list rows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Medium Text"
android:id="#+id/textView"/>
</LinearLayout>
Now the code for the adapter looks like this:
EDIT changed from ArrayAdapter to BaseAdapter and added a view holder (see comments):
public class MyAdapter extends BaseAdapter
{
private ArrayList<Job> mDatalist;
private int mLayoutID;
private Activity mCtx;
private MyAdapter(){} // the adapter won't work with the standard constructor
public MyAdapter(Activity context, int resource, ArrayList<Job> objects)
{
super();
mLayoutID = resource;
mDatalist = objects;
mCtx = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
if (rowView == null) {
LayoutInflater inflater = mCtx.getLayoutInflater();
rowView = inflater.inflate(mLayoutID, null);
ViewHolder viewHolder = new ViewHolder();
viewHolder.tvDescription = (TextView) rowView.findViewById(R.id.textView);
rowView.setTag(viewHolder);
}
ViewHolder vholder = (ViewHolder) rowView.getTag();
TextView tvJob = vholder.tvDescription;
Job myJob = mDatalist.get(position);
tvJob.setText(myJob.getJobDescription());
if (myJob.isDone())
{
// apply changes to TextView
tvJob.setPaintFlags(tvJob.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
tvJob.setTextColor(Color.GRAY);
}
else
{
// show TextView as usual
tvJob.setPaintFlags(tvJob.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
tvJob.setTextColor(Color.BLACK); // or whatever is needed...
}
return rowView;
}
#Override
public int getCount()
{
return mDatalist.size();
}
#Override
public Object getItem(int position)
{
return mDatalist.get(position);
}
#Override
public long getItemId(int position)
{
return position;
}
static class ViewHolder
{
public TextView tvDescription;
}
}
Due to the changed adapter,
in the MainActivity, you have to declare 'items' and 'itemsAdapter' as follows:
private ArrayList<Job> items;
private MyAdapter itemsAdapter;
...and in your 'onCreate()' method, you write:
itemsAdapter = new MyAdapter<String>(this, R.layout.mytextviewlayout, items);
Don't forget to change the 'readItems()' and 'writeItems()' methods because 'items' now is a ArrayList< Job >.
Then, finally, the 'onItemLongClick()' method:
EDIT use 'parent.getItemAtPosition()' instead of 'items.get()', see comments
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
// items.get(position).setDone(true);
Object o = parent.getItemAtPosition(position);
if (o instanceof Job)
{
((Job) o).setDone(true);
}
// and now indeed the data set has changed :)
itemsAdapter.notifyDataSetChanged();
writeItems();
return true;
}
Hi I'm currently looking at the best/simplest way to populate a spinner with the values from a select section of a html page. Eventually the spinner values must be exactly the same as the ones present in the html select section. I'm looking to do this in the simplest way possible. I thought of the following ideas:
Read the values from the html page (for example using lxml)
Add the values to the spinner (directly or if not possible after saving the values in a database)
Does anyone know the simplest way to this (for both the read part and the population part)? Is there an android object/class allowing to directly link the values from the html page to a spinner?
Many thanks in for your help!
Ben
I used jsoup in an AsyncTask to get the value and text of the options and put them in a text/value TreeMap (sorted HashMap) like so:
class TheaterGetter extends AsyncTask<Context, Void, Document> {
private Context context;
#Override
protected Document doInBackground(Context... contexts) {
context = contexts[0];
Document doc = null;
try {
doc = Jsoup.connect("http://landmarkcinemas.com").timeout(10000).get();
} catch (IOException e) {
Log.e("website connection error", e.getMessage());
}
return doc;
}
protected void onPostExecute(Document doc) {
Element allOptions = doc.select("select[id=campaign").first();
Elements options = allOptions.getElementsByTag("option");
options.remove(0);
TreeMap<String, String> theaters = new TreeMap<String, String>();
for (Element option:options) {
theaters.put(option.html(), option.attr("value"));
}
Then I made this adapter for the spinner:
public class TreeMapSpinAdapter extends ArrayAdapter{
private Context context;
private TreeMap<String, String> treeMap;
public TreeMapSpinAdapter(Context context, int textViewResourceId, TreeMap<String, String> treeMap){
super(context, textViewResourceId, treeMap.values().toArray());
this.context = context;
this.treeMap = treeMap;
}
#Override
public int getCount() {
return this.treeMap.values().size();
}
#Override
public Object getItem(int arg0) {
return this.treeMap.values().toArray()[arg0];
}
public Object getItem(String key) {
return treeMap.get(key);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView label = new TextView(context);
label.setTextColor(Color.BLACK);
label.setText(treeMap.keySet().toArray()[position].toString());
return label;
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
TextView label = new TextView(context);
label.setTextColor(Color.BLACK);
label.setText(treeMap.keySet().toArray()[position].toString());
return label;
}
}
Then, back in our AsyncTask we set up the spinner like so:
TreeMapSpinAdapter adapter = new TreeMapSpinAdapter(context, android.R.layout.simple_spinner_item, theaters);
final Spinner spinner = (Spinner) ((Activity) context).findViewById(R.id.spinner1);
spinner.setAdapter(adapter);
And finally we call our AsyncTask like so:
new TheaterGetter().execute(this);
Things are called theater this and that because in my case I was getting a list of theater locations.
I have a listview with a button in each row. I have made a custom Adapter class and a ItemModel class to hold the data for each row. Inside the ItemModel class I have defined an ActionListener for the button. How can I call a method in another class from inside my button's action listener?
Right now if i say Classname clsName = new Classname(); and inside the actionlistener do clsName.methodName(variableToPass); <--- this all compiles but crashes when I click the button..Anyone know how to get this to work?
MyListModel Class
public class MyListItemModel{ //that's our book
private String title; // the book's title
private String description; //the book's description
int id; //book owner id
String key; //book key
private Context context;
Shelf shelf = new Shelf(); //shelf class
public MyListItemModel(Context c){
this.context=c;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getKey() {
return key;
}
public void setKey(String key){
this.key = key;
}
OnClickListener listener = new OnClickListener(){ // the book's action
#Override
public void onClick(View v) {
//code for the button action
//THIS DOESN'T WORK PROPERLY AND CRASHES ON CLICK. However if i use a Toast to print the key on each click - it will print the right key to screen.
shelf.downloadBook(new String(key));
}
};
int getBookId(){
return title.hashCode();
}
}
MyListAdapter class - method for getView
public class MyListAdapter extends BaseAdapter {
View renderer;
List<MyListItemModel> items;
ArrayList<HashMap<String, String>> mylist;
private LayoutInflater mInflater;
private Context context;
public MyListAdapter(Context c){
this.context=c;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
....
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null){
//convertView = renderer;
convertView = mInflater.inflate(R.layout.shelfrow, null);
}
MyListItemModel item = items.get(position);
TextView label = (TextView)convertView.findViewById(R.id.item_title);
label.setText(item.getTitle());
TextView label2 = (TextView)convertView.findViewById(R.id.item_subtitle);
label2.setText(item.getDescription());
Button button = (Button)convertView.findViewById(R.id.btn_download);
button.setOnClickListener(item.listener);
return convertView;
}
My Shelf class has a method called downloadBook(String bookKey) <-- this is what I want to call with each button click and pass this method the appropriate book key. I also have 2 xml files (shelfrow.xml and shelflist.xml). One contains the textfields and button and the other contains the listview.
Some of the code from Shelf.java class
List<MyListItemModel> myListModel = new ArrayList<MyListItemModel>();
try{
JSONArray entries = json.getJSONArray("entries");
for(int i=0;i<entries.length();i++){
MyListItemModel item = new MyListItemModel(this);
JSONObject e = entries.getJSONObject(i);
item.id = i; //user ID
bookKey = (e.getString("key"));
item.setTitle(e.getString("title"));
item.setDescription(e.getString("description"));
myListModel.add(item);
}
}catch(JSONException e) {
Log.e("log_tag", "Error parsing data "+e.toString());
}
MyListAdapter adapter = new MyListAdapter(this);
adapter.setModel(myListModel);
setListAdapter(adapter);
lv = getListView();
lv.setTextFilterEnabled(true);
….
public void downloadBook(String theKey) {
//take theKey and append it to a url address to d/l
}
Stacktrace from logcat
05-23 02:34:59.439: INFO/wpa_supplicant(14819): Reset vh_switch_counter due to receive LINKSPEED cmd 05-23 02:34:59.439: DEBUG/ConnectivityService(1346): getMobileDataEnabled returning true 05-23 02:36:39.269: DEBUG/StatusBarPolicy(6068): onSignalStrengthsChange
also this came up zygoteinit methodandargscaller.run
I am going to go out on a limb here, but I think I know what the issue is.
In the last snippet of code, you are not setting the key field on the MyListItemModel. You are instead setting some variable called 'bookKey' (I do not see where it is defined).
I bet if you change this line:
bookKey = (e.getString("key"));
to be this:
item.setKey(e.getString("key"));
//or item.key = e.getString("key"));
everything will work just fine for you. If you pass in a null String to the String(String) constructor, you will get a NullPointerException, as that constructor expects a non-null String.
I will mention that it is not necessary for you use the String(String) constructor, you should be fine just doing this in the fist snippet:
shelf.downloadBook(key);