Repository: beeware/toga

Issue:
A single column table leads to only showing first letter of value
## Expected Behavior

Table like this:
**Filename**
xxx
yyy
zzz

## Current Behavior
**Filename**
x
y
z

## Steps to reproduce

Use toga.Table(headings=['Filename'], data=['xxx', 'yyy', 'zzz'], on_select=self.handle_name_select)

## Analysis
The problem seems to be in the ListSource class.
`def _create_row(self, data):
        if isinstance(data, dict):
            row = Row(**data)
        else:
            row = Row(**dict(zip(self._accessors, data)))
        row._source = self
        return row'

In list_source.py line 56 it says:

`row = Row(**dict(zip(self._accessors, data)))`

but the data parameter is a string when using a list of strings as data, leading to the zipping of the individual characters. When passing in the data as [('xxx',), ('yyy',), ('zzz',)] the error does not occur.

So either the API should make it explicit that it expects a list of lists, or handle the data-is-a-list-of-strings case correctly


Discussion:

Resolving patch:
diff --git a/src/core/toga/sources/list_source.py b/src/core/toga/sources/list_source.py
--- a/src/core/toga/sources/list_source.py
+++ b/src/core/toga/sources/list_source.py
@@ -24,7 +24,7 @@ class ListSource(Source):
 
     Args:
         data (`list`): The data in the list. Each entry in the list should have the
-            same number of entries as there are accessors.
+            same number of entries as there are accessors. 
         accessors (`list`): A list of attribute names for accessing the value
             in each column of the row.
     """
@@ -50,10 +50,20 @@ def __getitem__(self, index):
     ######################################################################
 
     def _create_row(self, data):
+        """Create a Row object from the given data.
+        Args:
+            data (any): The type of `data` determines how it is handled
+                ``dict``: each key corresponds to a column accessor
+                iterables, except ``str`` and ``dict``: each item corresponds to a column
+                all else: `data` will fill the first column
+        """
+
         if isinstance(data, dict):
             row = Row(**data)
-        else:
+        elif hasattr(data, '__iter__') and not isinstance(data, str):
             row = Row(**dict(zip(self._accessors, data)))
+        else:
+            row = Row(**{self._accessors[0]: data})
         row._source = self
         return row
 


Test patch:
diff --git a/src/core/tests/sources/test_list_source.py b/src/core/tests/sources/test_list_source.py
--- a/src/core/tests/sources/test_list_source.py
+++ b/src/core/tests/sources/test_list_source.py
@@ -122,6 +122,63 @@ def test_init_with_dict(self):
 
         listener.insert.assert_called_once_with(index=1, item=source[1])
 
+    def test_init_with_flat_list_of_objects(self):
+        "A list source can be created from a flat list of objects"
+
+        class MyObject:
+            def __init__(self, info):
+                self.info = info
+            def __str__(self):
+                return "string value %s" % self.info
+
+        data = [
+            MyObject(True),
+            MyObject(2),
+            MyObject("string"),
+        ]
+
+        source = ListSource(
+            data=data,
+            accessors=['col1'],
+        )
+
+        for i, row in enumerate(source):
+            self.assertEqual(row.col1, data[i])
+
+    def test_init_with_flat_list_of_numbers(self):
+        "A list source can be created from a flat list of numbers"
+
+        data = [
+            100,
+            200.0,
+            -3.14,
+        ]
+
+        source = ListSource(
+            data=data,
+            accessors=['col1'],
+        )
+
+        for i, row in enumerate(source):
+            self.assertEqual(row.col1, data[i])
+
+    def test_init_with_flat_list_of_strings(self):
+        "A list source can be created from a flat list of strings"
+
+        data = [
+            "xxx",
+            "yyy",
+            "zzz",
+        ]
+
+        source = ListSource(
+            data=data,
+            accessors=['col1'],
+        )
+
+        for i, row in enumerate(source):
+            self.assertEqual(row.col1, data[i])
+
     def test_iter(self):
         "A list source can be iterated over"
         source = ListSource(