Forum Archive

Reminders: List Index Out of range

techteej

I get a list index bug at line 10, not quite sure why.

# coding: utf-8
import dialogs
import reminders
import ui

v = ui.load_view('reminders')
reminders_table = v['reminders']

def picked(sender):
    item = sender.items[sender.selected_row]
    r = item['reminder']
    r.completed = True
    r.save()
    del sender.items[sender.selected_row]

def grabbed():
    global todo_items, completed_items
    todo = reminders.get_reminders(completed=False)
    todo_items = [{'title': r.title, 'reminder': r} for r in todo]
    done = reminders.get_reminders(completed=True)
    completed_items = [{'title': r.title, 'reminder': r} for r in done]
    reminders_table.data_source = ui.ListDataSource(items=todo_items)
    reminders_table.data_source.action = picked
    reminders_table.reload()

def button_action(sender):
    if segment.selected_index == 0:
        reminders_table.data_source = ui.ListDataSource(items=todo_items)
        reminders_table.data_source.action = picked
        reminders_table.reload()
    elif segment.selected_index == 1:
        reminders_table.data_source = ui.ListDataSource(items=completed_items)
        reminders_table.data_source.action = picked
        reminders_table.reload()

@ui.in_background
def but_action(sender):
    fields = [{'key' : 'name', 'type' : 'text', 'value' : 'Name your reminder'},]
    result=dialogs.form_dialog(title='Create a Reminder', fields=fields)
    r = reminders.Reminder()
    r.title = result['name']
    r.save()
    segment.selected_index = 0
    grabbed()

segment = v['segmentedcontrol1']
segment.action = button_action
reminders_table.data_source.action = picked
create_button = ui.ButtonItem()
create_button.image = ui.Image.named('ionicons-ios7-plus-empty-32')
create_button.action = but_action
grabbed()
v.right_button_items = [create_button]
v.present('sheet')
ccc

Where can we find reminders.pyui?

techteej

[{"class":"View","attributes":{"name":"Reminders","background_color":"RGBA(1.000000,1.000000,1.000000,1.000000)","tint_color":"RGBA(0.000000,0.478000,1.000000,1.000000)","enabled":true,"border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","flex":""},"frame":"{{0, 0}, {540, 575}}","nodes":[{"class":"SegmentedControl","attributes":{"name":"segmentedcontrol1","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","uuid":"74975CE5-5D48-4308-871C-A0C96E4656F0","enabled":true,"segments":"To-Do|Done","flex":"LR"},"frame":"{{137, 6}, {265, 29}}","nodes":[]},{"class":"TableView","attributes":{"background_color":"RGBA(1.000000,1.000000,1.000000,1.000000)","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","data_source_number_of_lines":1,"enabled":true,"flex":"WH","row_height":44,"data_source_action":"picked","data_source_items":"","data_source_delete_enabled":false,"editing":false,"name":"reminders","uuid":"CEF3207F-E38F-409D-AB5D-695A3C52D0D8","data_source_font_size":18},"frame":"{{6, 43}, {528, 526}}","nodes":[]}]}]"

zencuke

Looks smelly to me. The sender.items list doesn't look right. It only has a single item so any selection except the top (row == 0) will fail with index out of range. Selecting the top item gets past that error but fails because the entry it doesn't have a "reminder" attribute.

The sender.items list has a single entry, a dictionary with the standard attributes but with empty values. I would have expected the items list to contain all table row values but it doesn't.

note: changing the assignment to:

  item = reminders_table.data_source.items[sender.selected_row]

seems to work. At least it stops generating error messages.

Summary: I'm guessing the sender variable is being trashed before the call. Maybe a ui bug. The code does reassign data_source many times. Maybe a missing update.

zencuke

Forgot to mention: I would have expected sender to equal

reminders_table.data_source

in picked but it doesn't. That probably says something. I'm not sure what.

zencuke
Another note: I've never used the built-in list_data_source so my knowledge is shaky.
I generate TableViewCells directly in my own data_source. However if I did I would try to
create a single ui.ListDataSource object at startup and update its contents when needed
instead of creating a new one every time the contents change.
ccc

Each time you have the line:

reminders_table.data_source = ui.ListDataSource(items=SomethingOrOther)

Immediately follow that with the line:

reminders_table.delegate = reminders_table.data_source

That would alone would solve the problem that you mentioned but I would also recommend simplifying button_action() to be just:

def button_action(sender):
    if segment.selected_index:
        reminders_table.data_source.items = completed_items
    else:
        reminders_table.data_source.items = todo_items

So that you reuse the same ListDatasource instance over and over again instead of allocating a new one each time the data changes.

techteej

@ccc Thanks for this, only problem is that when a reminder is marked as completed, you need to re run the script for it to be in the completed items.

ccc

You could create a function like:

def get_reminder_items(completed=False):
    return [{'title': r.title, 'reminder': r}
                for r in reminders.get_reminders(completed=completed)]

which you could call (reminders_table.data_source.items = get_reminder_items(completed=TrueOrFalse) from picked(), grabbed(), and button_action() to put fresh data into the ListDataSource.

techteej

@ccc Using your help, I was able to delete grabbed() all together

# coding: utf-8
import dialogs
import reminders
import ui

v = ui.load_view('reminders')
reminders_table = v['reminders']

def get_reminder_items(completed=False):
    return [{'title': r.title, 'reminder': r}
            for r in reminders.get_reminders(completed=completed)]

def picked(sender):
    item = sender.items[sender.selected_row]
    r = item['reminder']
    r.completed = True
    r.save()
    del sender.items[sender.selected_row]

def button_action(sender):
    if segment.selected_index:
        reminders_table.data_source.items = get_reminder_items(completed=True)
    else:
        reminders_table.data_source.items = get_reminder_items(completed=False)

@ui.in_background
def but_action(sender):
    fields = [{'key' : 'name', 'type' : 'text', 'value' : 'Name your reminder'},]
    result=dialogs.form_dialog(title='Create a Reminder', fields=fields)
    r = reminders.Reminder()
    r.title = result['name']
    r.save()
    segment.selected_index = 0
    reminders_table.data_source.items = get_reminder_items(completed=False)

segment = v['segmentedcontrol1']
segment.action = button_action
reminders_table.data_source.action = picked
create_button = ui.ButtonItem()
create_button.image = ui.Image.named('ionicons-ios7-plus-empty-32')
create_button.action = but_action
reminders_table.data_source.items = get_reminder_items(completed=False)
v.right_button_items = [create_button]
v.present('sheet')
ccc

Cool... As I user, I would find it a bit of a shock that just tapping an incomplete task moves it to a completed state with no chance to undo that action but you might want your app to work that way.

One super minor suggestion:

def button_action(sender):
    completed = segment.selected_index == 1
    reminders_table.data_source.items = get_reminder_items(completed=completed)
zencuke

That's a nice script. Thanks. I was just about to add reminders to my app and this will be a great demo. It is also interesting that you don't need to create a ListDataSource at all.

JonB

When you create a TableView from the ui builder, it creates a ListDataSource for you. If you were creating this from code, I believe you do need to specify a delegate and data source.

techteej

@ccc and @zencuke It's actually part of a something bigger I've been working on and thanks to ccc I've been able to move on to adding more features. Just added the calendar ability.

# coding: utf-8
import console
import dialogs
import reminders
import ui

v = ui.load_view('reminders')
reminders_table = v['reminders']

def get_reminder_items(completed=False):
    return [{'title': r.title, 'reminder': r}
            for r in reminders.get_reminders(completed=completed)]

def picked(sender):
    item = sender.items[sender.selected_row]
    r = item['reminder']
    if r.completed == True:
        r.completed = False
    else:
        r.completed = True
    r.save()
    del sender.items[sender.selected_row]

def button_action(sender):
    completed = segment.selected_index == 1
    reminders_table.data_source.items = get_reminder_items(completed=completed)

@ui.in_background
def but_action(sender):
    fields = [{'key' : 'name', 'type' : 'text', 'value' : 'Name your reminder'},
              {'key' : 'calendar', 'type' : 'text', 'value' : 'Name a calendar for this reminder'}]
    result=dialogs.form_dialog(title='Create a Reminder', fields=fields)
    all_calendars = reminders.get_all_calendars()
    for calendar in all_calendars:
        if calendar.title == result['calendar']:
            r = reminders.Reminder(calendar)
            r.title = result['name']
            r.save()
            break
    else:
        q = console.alert('Could not find calendar', 'Could not find calendar named ' + result['calendar'] + ' Would you like to create one?', 'Yes', hide_cancel_button=False)
        if q == 1:
            new_calendar = reminders.Calendar()
            new_calendar.title = result['calendar']
            new_calendar.save()
            calendar.title == result['calendar']
            r = reminders.Reminder(calendar)
            r.title = result['name']
            r.save()

    segment.selected_index = 0
    reminders_table.data_source.items = get_reminder_items(completed=False)
    #console.hud_alert('Reminder Created', 'success', 1)

segment = v['segmentedcontrol1']
segment.action = button_action
reminders_table.data_source.action = picked
create_button = ui.ButtonItem()
create_button.image = ui.Image.named('ionicons-ios7-plus-empty-32')
create_button.action = but_action
reminders_table.data_source.items = get_reminder_items(completed=False)
v.right_button_items = [create_button]
v.present('sheet')
ccc
    if r.completed == True:
        r.completed = False
    else:
        r.completed = True

# can be replaced by

    r.completed = not r.completed