Repository: kivy/kivy

Issue:
Allow using kv ids in RecycleView data keys
**Software Versions**
* Python: 3.6.9 
* OS: Ubuntu 18.04.4 LTS
* Kivy: 1.11.0
* Kivy installation method: PIP

**Describe the bug**
The official information regarding recycleview shows the following: 
```yaml
<RV>:
    viewclass: 'Label' ### Here
    RecycleBoxLayout:
        default_size: None, dp(56)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'
```

Where viewclass is a simple Label widget with the attribute "text".  Created Label widgets within RV are populated as follows:

```python
class RV(RecycleView):
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.data = [{'text': str(x)} for x in range(100)] ###### here one can see the class attribute 'text'
```

If one creates a more complicated widget for the viewclass within RecycleView as follows: 

```yaml
<MyRecycleView>:
    viewclass: 'ThisInfo' ### Mix of widgets
    RecycleBoxLayout:
        orientation: 'vertical'
        default_size: None, dp(120)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        spacing: 3

<ThisInfo@BoxLayout>:
    BoxLayout:
        size_hint_x: 1.3
        orientation: 'vertical'
        Label:
            id: name                 ### I should be able to modify this 'text' attribute from self.data by its id
        GridLayout:
            cols: 2
            Label:
                text: 'Position'
            Label:
                text: 'Salary'
            Label:
                id: position         ## Same here
            Label:
                id: salary           ### And here
```

And tries to populate in any of this ways, it does not populate any info, all the id fields appear blank.

```python
############### Method 1, placing the ID and attribute of the pertinent widget ###############
class MyRecycleView(RecycleView):
    def __init__(self,  **kwargs):
        super(MyRecycleView, self).__init__(**kwargs)

        self.data = [{'name.text': employee.name, 
                            'position.text': employee.position, 
                            'salary.text': employee.salary} 
                                 for employee in all_employees] 

######## Method 2, placing Id as the key of the widget and a dictionary for all of its attributes #####
class MyRecycleView(RecycleView):
    def __init__(self,  **kwargs):
        super(MyRecycleView, self).__init__(**kwargs)

        self.data = [{'name': {'text': employee.name}, 
                           'position': {'text': employee.position},
                           'salary': {'text':  employee.salary}} 
                               for employee in all_employees]
```

**Expected behavior**
The behavior is not consistent, when using a base class as Label, one may populate its attributes directly in the self.data list with 'text'. When using a mixture of widgets with separate ids one should be able to populate its attributes by its ID just as in all the rest of the kivy framework.


Discussion:
that's interesting, my way of achieving this is to map the properties of the internal widgets to properties on the root widget, and populate them there.

```yaml
<ThisInfo@BoxLayout>:
    name_text: ""
    salary_text: ""
    position_text: ""
    BoxLayout:
        size_hint_x: 1.3
        orientation: 'vertical'
        Label:
            text: root.name_text or ""
        GridLayout:
            cols: 2
            Label:
                text: 'Position'
            Label:
                text: 'Salary'
            Label:
                text: root.position_text or ""
            Label:
                text: root.salary_text or ""
```

and in python
```python
        self.data = [{'name_text': employee.name, 
                           'position_text': employee.position,
                           'salary_text':  employee.salary} 
                               for employee in all_employees]
```

Feel free to propose a pull request if you want your proposed way to work, but as you can see the current way allow filling the need pretty easily, while (imho) defining the interface of the widget more clearly.
I pretty much agree with what @tshirtman said and that has also been my workaround. 

I would probably reject such a PR though tbh, because it would complicate the rv code. Right now it's basically just a `setattr` in a loop, but this would add needles complexity for the most part, when there's a simple workaround.

A PR that would be more acceptable, is perhaps providing subclasses of `RecycleDataViewBehavior` and `RecycleDataViewBehavior` that do this. That way people can "opt-in" to this behavior and that would probably be a net improvement. Perhaps I may even work on this, if no one else does.
I've used that work around before, and I believe that is the problem, it feels like a work around and not the optimal way to do it. 

Programming is all about logic and the logic thing to do in the scenario shared would be to populate the objects attributes directly following kivy's logic of programming, instead of creating aux variables, populate them and then use them to populate the objects attributes. 

Resolving patch:
diff --git a/examples/widgets/recycleview/basic_data.py b/examples/widgets/recycleview/basic_data.py
--- a/examples/widgets/recycleview/basic_data.py
+++ b/examples/widgets/recycleview/basic_data.py
@@ -1,4 +1,4 @@
-from random import sample
+from random import sample, randint
 from string import ascii_lowercase
 
 from kivy.app import App
@@ -7,7 +7,7 @@
 
 
 kv = """
-<Row@BoxLayout>:
+<Row@RecycleKVIDsDataViewBehavior+BoxLayout>:
     canvas.before:
         Color:
             rgba: 0.5, 0.5, 0.5, 1
@@ -15,6 +15,8 @@
             size: self.size
             pos: self.pos
     value: ''
+    Label:
+        id: name
     Label:
         text: root.value
 
@@ -88,21 +90,24 @@
 class Test(BoxLayout):
 
     def populate(self):
-        self.rv.data = [{'value': ''.join(sample(ascii_lowercase, 6))}
-                        for x in range(50)]
+        self.rv.data = [
+            {'name.text': ''.join(sample(ascii_lowercase, 6)),
+             'value': str(randint(0, 2000))}
+            for x in range(50)]
 
     def sort(self):
-        self.rv.data = sorted(self.rv.data, key=lambda x: x['value'])
+        self.rv.data = sorted(self.rv.data, key=lambda x: x['name.text'])
 
     def clear(self):
         self.rv.data = []
 
     def insert(self, value):
-        self.rv.data.insert(0, {'value': value or 'default value'})
+        self.rv.data.insert(0, {
+            'name.text': value or 'default value', 'value': 'unknown'})
 
     def update(self, value):
         if self.rv.data:
-            self.rv.data[0]['value'] = value or 'default new value'
+            self.rv.data[0]['name.text'] = value or 'default new value'
             self.rv.refresh_from_data()
 
     def remove(self):
diff --git a/kivy/factory_registers.py b/kivy/factory_registers.py
--- a/kivy/factory_registers.py
+++ b/kivy/factory_registers.py
@@ -182,6 +182,7 @@
 r('LayoutSelectionBehavior', module='kivy.uix.recycleview.layout')
 r('RecycleLayoutManagerBehavior', module='kivy.uix.recycleview.layout')
 r('RecycleDataViewBehavior', module='kivy.uix.recycleview.views')
+r('RecycleKVIDsDataViewBehavior', module='kivy.uix.recycleview.views')
 r('RecycleDataAdapter', module='kivy.uix.recycleview.views')
 r('RecycleDataModelBehavior', module='kivy.uix.recycleview.datamodel')
 r('RecycleDataModel', module='kivy.uix.recycleview.datamodel')
diff --git a/kivy/uix/recycleview/views.py b/kivy/uix/recycleview/views.py
--- a/kivy/uix/recycleview/views.py
+++ b/kivy/uix/recycleview/views.py
@@ -17,11 +17,14 @@
 
 '''
 
-from kivy.properties import StringProperty, ObjectProperty
+from kivy.properties import ObjectProperty
 from kivy.event import EventDispatcher
-from kivy.factory import Factory
 from collections import defaultdict
 
+__all__ = (
+    'RecycleDataViewBehavior', 'RecycleKVIDsDataViewBehavior',
+    'RecycleDataAdapter')
+
 _view_base_cache = {}
 '''Cache whose keys are classes and values is a boolean indicating whether the
 class inherits from :class:`RecycleDataViewBehavior`.
@@ -108,6 +111,44 @@ def apply_selection(self, rv, index, is_selected):
         pass
 
 
+class RecycleKVIDsDataViewBehavior(RecycleDataViewBehavior):
+    """Similar to :class:`RecycleDataViewBehavior`, except that the data keys
+    can signify properties of an object named with an id in the root KV rule.
+
+    E.g. given a KV rule::
+
+        <MyRule@RecycleKVIDsDataViewBehavior+BoxLayout>:
+            Label:
+                id: name
+            Label:
+                id: value
+
+    Then setting the data list with
+    ``rv.data = [{'name.text': 'Kivy user', 'value.text': '12'}]`` would
+    automatically set the corresponding labels.
+
+    So, if the key doesn't have a period, the named property of the root widget
+    will be set to the corresponding value. If there is a period, the named
+    property of the widget with the id listed before the period will be set to
+    the corresponding value.
+
+    .. versionadded:: 2.0.0
+    """
+
+    def refresh_view_attrs(self, rv, index, data):
+        sizing_attrs = RecycleDataAdapter._sizing_attrs
+        for key, value in data.items():
+            if key not in sizing_attrs:
+                name, *ids = key.split('.')
+                if ids:
+                    if len(ids) != 1:
+                        raise ValueError(
+                            f'Data key "{key}" has more than one period')
+                    setattr(self.ids[name], ids[0], value)
+                else:
+                    setattr(self, name, value)
+
+
 class RecycleDataAdapter(EventDispatcher):
     '''The class that converts data to a view.
 


Test patch: