title: Take user input from Kivy App and append to google sheets using Python
body: <p>I'm trying to make a simple Python Kivy app (using a kv file for the layout) that I will use to keep inventory of my parts for work. I am very new to python and kivy, so I need a little bit of direction on this.</p>
  <p>One of the basic functions will be to allow the user to input a new part on the &quot;Add Part to Inventory&quot; screen (one of four screens).  The user input consists of four items (part name, serial number, on-hand quantity, and the minimum amount needed).</p>
  <p>Once the user enters this info and presses the 'Submit' button, I would like that data to be appended to a new row in a google sheet that I'm using as a backend database using the Google Drive/Sheets API (which I was able to get working).</p>
  <p>Most of the example apps that I've found online only have one screen, and are only dealing with one type of user input (usually text, whereas my input will be text and integers).</p>
  <p>I'm confused as to where I need to put my ids in the kv file (I'm assuming under the AddPartWindow layout), how to call a function when a button is pressed that is not in the root class, how to store the user input into a list since I'm not sure what the ObjectProperty function is really doing, and finally, how to append that list to a new row in my google sheet.</p>
  <p>I'm not looking for a cut and paste answer, just some direction would be nice as I have not been able to find a good reference for exactly what I'm trying to do.  My main confusion lies in having multiple screens, and how to transfer data between the different screens and their respective classes.  Should data only be funneled through a single root class?  Or can each class be used to handle data coming from their respective screens (windows) in the app?  What exactly is the ObjectProperty function doing?  I find the kivy website to be a bit convoluted when describing the properties class.</p>
  <p>Any help/direction woud be greatly appreciated.</p>
  <p>Here is the main.py code:</p>
  <pre><code>import kivy
  from kivy.app import App
  from kivy.uix.gridlayout import GridLayout
  from kivy.uix.screenmanager import ScreenManager, Screen
  from kivy.core.window import Window
  from kivy.uix.button import Button
  from kivy.properties import ObjectProperty
  import gspread
  from oauth2client.service_account import ServiceAccountCredentials
  
  scope = ['https://www.googleapis.com/auth/drive']
  creds = ServiceAccountCredentials.from_json_keyfile_name('My_First_Project-3d753d98320e.json', scope)
  client = gspread.authorize(creds)
  iQue_sheet = client.open(&quot;InventoryBackend&quot;).sheet1
  
  #root layout
  class InventoryWindow(Screen):
      pass
  
  #Layout in question
  class AddPartWindow(Screen):
  
      #Is there anything else I need to do with these before saving into a list or dictionary?
      
      part_name = ObjectProperty(None)
      serial_number = ObjectProperty(None)
      on_hand_cnt = ObjectProperty(None)
      min_needed = ObjectProperty(None)
  
      #This function should save the user input into a list, np, and then append to the google sheet iQue_sheet
      #Wasn't sure if it should be a list or a dictionary.
      #I'm assuming .text is type-casting each object to a string.  Can this be used for numerical inputs?
      
      def new_part(self):
          np = [self.part_name.text, self.serial_number.text, self.on_hand_cnt.text, self.min_needed.text]
          iQue_sheet.append(np)
  
  class OnOrderWindow(Screen):
      pass
  
  class OrderFormWindow(Screen):
      pass
  
  class WindowManager(ScreenManager):
      pass
  
  class InventoryApp(App):
      def build(self):
          #These are used to enable going back and forth between screens using buttons
          sm = ScreenManager()
          sm.add_widget(InventoryWindow(name='inv_window'))
          sm.add_widget(OnOrderWindow(name='on_order_window'))
          sm.add_widget(AddPartWindow(name='add_part_window'))
          sm.add_widget(OrderFormWindow(name='order_form_window'))
          return sm
  
  if __name__ == &quot;__main__&quot;:
      InventoryApp().run()
  
  </code></pre>
  <p>Here is the my .kv file for the layout, the Add Part window is the last layout towards the end:</p>
  <pre><code>WindowManager:
      InventoryWindow:
      OnOrderWindow:
      OrderFormWindow:
      AddPartWindow:
  
  &lt;ItemLabel@Label&gt;
      font_size: '15sp'
      halign: 'left'
      valign: 'middle'
      text_size: self.size
  
  &lt;ItemButton@Button&gt;
      pos_hint: {'center_x':0.5, 'center_y':0.5}
      size_hint: 0.65, 0.2
  
  
  &lt;ItemButton2@Button&gt;
      pos_hint: {'center_x':0.5, 'center_y':0.5}
      size_hint: 0.65, 0.2
      on_release:
          app.root.current = &quot;order_form_window&quot;
  
  
  &lt;InventoryWindow&gt;:
      name: &quot;inv_window&quot;
          add_probe: add_probe
  
      FloatLayout:
          Label:
              text: 'Inventory'
              font_size: '25sp'
              size_hint: (1, 0.17)
              pos_hint: {'x': 0, 'y': 0.87}
  
      GridLayout:
          cols: 4
  
          padding:[10, 65, 10, 10]
          spacing: 5
  
          BoxLayout:
              orientation: 'vertical'
              ItemLabel:
                  text:'iQue3: Probe/Tubing'
  
          BoxLayout:
              orientation: 'vertical'
  
              ItemButton:
                  text:'Add'
                  on_release:
                      root.new_part()
  
          BoxLayout:
              orientation: 'vertical'
  
              ItemButton:
                  text:'Sub'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton2:
                  text:'Order'
  
          BoxLayout:
              orientation: 'vertical'
              ItemLabel:
                  text:'Gen2: Probe/Tubing'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton:
                  text:'Add'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton:
                  text:'Sub'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton2:
                  text:'Order'
  
          BoxLayout:
              orientation: 'vertical'
              ItemLabel:
                  text:'Beads'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton:
                  text:'Add'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton:
                  text:'Sub'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton2:
                  text:'Order'
  
          BoxLayout:
              orientation: 'vertical'
              ItemLabel:
                  text:'iQue3 Fluid Maint. Kit'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton:
                  text:'Add'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton:
                  text:'Sub'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton2:
                  text:'Order'
  
          BoxLayout:
              orientation: 'vertical'
              ItemLabel:
                  text:'Screener Fluid Maint. Kit'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton:
                  text:'Add'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton:
                  text:'Sub'
  
          BoxLayout:
              orientation: 'vertical'
              ItemButton2:
                  text:'Order'
  
          BoxLayout:
              orientation: 'vertical'
  
  
      GridLayout:
          cols: 2
          size_hint: 1, 0.15
          padding: [10, 0, 10, 10]
  
  
          BoxLayout:
  
              Button:
                  text: 'Add Part to Inventory'
                  font_size: '18sp'
                  size_hint: (1, 0.75)
                  on_release:
                      app.root.current = &quot;add_part_window&quot;
                      root.manager.transition.direction = &quot;right&quot;
  
              Button:
                  text: 'On Order'
                  font_size: '18sp'
                  size_hint: (1, 0.75)
                  on_release:
                      app.root.current = &quot;on_order_window&quot;
                      root.manager.transition.direction = &quot;left&quot;
  
  
  &lt;OnOrderWindow&gt;:
  
      name: &quot;on_order_window&quot;
  
      FloatLayout:
          Label:
              text: 'On Order'
              font_size: '25sp'
              size_hint: (1, 0.17)
              pos_hint: {'x': 0, 'y': 0.87}
  
          GridLayout:
              cols: 2
              size_int: 1, .15
              padding:[10, 510, 10, 10]
  
              Button:
                  text: &quot;Inventory&quot;
                  font_size: '18sp'
                  size_hint: (1, 0.6)
                  on_release:
                      app.root.current = &quot;inv_window&quot;
                      root.manager.transition.direction = &quot;right&quot;
              Button:
                  text:&quot;Add Part to Inventory&quot;
                  size_hint: (1, 0.6)
                  font_size: '18sp'
                  on_release:
                      app.root.current = &quot;add_part_window&quot;
                      root.manager.transition.direction = &quot;left&quot;
  
  
  &lt;OrderFormWindow&gt;
      name: &quot;order_form_window&quot;
  
      FloatLayout:
          Label:
              text: 'Order Form'
              font_size: '25sp'
              size_hint: (1, 0.17)
              pos_hint: {'x': 0, 'y': 0.87}
  
          GridLayout:
              cols:2
              padding: [50, 50, 50, 120]
  
              ItemLabel:
                  text: &quot;Part: &quot;
  
              ItemLabel:
                  text: &quot;Populate Part Here&quot;
  
              ItemLabel:
                  text: &quot;Serial Number: &quot;
  
              ItemLabel:
                  text: &quot;Populate SN Here&quot;
  
              ItemLabel:
                  text: &quot;On Hand: &quot;
  
              ItemLabel:
                  text: &quot;Populate On Hand Count Here&quot;
  
              ItemLabel:
                  text: &quot;Minimum Needed: &quot;
  
              ItemLabel:
                  text: &quot;Populate Min Needed Here&quot;
  
              ItemLabel:
                  text: &quot;Order Quantity&quot;
  
              TextInput:
                  halign: 'left'
                  valign: 'middle'
                  input_type: 'number'
                  input_filter: 'int'
                  multiline:False
  
          GridLayout:
              cols:2
              size_hint: 1, 0.15
              padding: [10, 0, 10, 10]
  
              Button:
                  text:&quot;Cancel&quot;
                  on_release: app.root.current = &quot;inv_window&quot;
  
              Button:
                  text:&quot;Submit Order&quot;
                  on_release: app.root.current = &quot;on_order_window&quot;
  
  
  #This is the add part screen layout I'm referring to
  &lt;AddPartWindow&gt;
      name: &quot;add_part_window&quot;
  
          #These are the id's I was referring to:
          
          part_name: part_name
          serial_number: serial_number
          on_hand_cnt: on_hand_cnt
          min_needed: min_needed
  
      FloatLayout:
          Label:
              text: 'Add Part to Inventory'
              font_size: '25sp'
              size_hint: (1, 0.17)
              pos_hint: {'x': 0, 'y': 0.86}
  
          GridLayout:
              cols:2
              padding: [50, 100, 50, 120]
              spacing: 5
  
              ItemLabel:
                  text: &quot;Name of Part: &quot;
  
              TextInput:
                  id: part_name
                  halign: 'left'
                  valign: 'middle'
                  multinline:False
  
              ItemLabel:
                  text: &quot;Serial Number: &quot;
  
              TextInput:
                  id: serial_number
                  halign: 'left'
                  valign: 'middle'
                  multiline:False
  
              ItemLabel:
                  text: &quot;How Many On Hand?: &quot;
  
              TextInput:
                  id: on_hand_cnt
                  halign: 'left'
                  valign: 'middle'
                  multinline:False
  
              ItemLabel:
                  text: &quot;Minimum Needed?: &quot;
  
              TextInput:
                  id: min_needed
                  halign: 'left'
                  valign: 'middle'
                  multiline:False
  
          GridLayout:
              cols:2
              size_hint: 1, 0.15
              padding: [10, 0, 10, 10]
  
              Button:
                  text:&quot;Cancel&quot;
                  on_release:
                      app.root.current = &quot;inv_window&quot;
                      root.manager.transition.direction = &quot;left&quot;
  
              #Here is the button I'm referring to, I realize that putting &quot;root.&quot; in front of new_part() is not 
              #the correct thing to put, so it's a placeholder for now:
              
              Button:
                  text:&quot;Submit&quot;
                  on_release:
                      root.new_part()
                      app.root.current = &quot;inv_window&quot;
                      root.manager.transition.direction = &quot;left&quot;
  
  </code></pre>
  <p><a href="https://i.stack.imgur.com/xSCir.png" rel="nofollow noreferrer">Here is a screen shot of the Add Part Screen</a></p>
  <p>The current error I'm getting when I enter input into the four fields and click on the Submit button is this:</p>
  <pre><code>File &quot;C:\Users\edr27\kivy_venv\lib\site-packages\kivy\uix\behaviors\button.py&quot;, line 179, in on_touch_up
       self.dispatch('on_release')
     File &quot;kivy\_event.pyx&quot;, line 705, in kivy._event.EventDispatcher.dispatch
     File &quot;kivy\_event.pyx&quot;, line 1248, in kivy._event.EventObservers.dispatch
     File &quot;kivy\_event.pyx&quot;, line 1132, in kivy._event.EventObservers._dispatch
     File &quot;C:\Users\edr27\kivy_venv\lib\site-packages\kivy\lang\builder.py&quot;, line 57, in custom_callback
       exec(__kvlang__.co_value, idmap)
     File &quot;C:\Users\edr27\PycharmProjects\pythonProject\inventory.kv&quot;, line 345, in &lt;module&gt;
       root.new_part()
     File &quot;C:\Users\edr27\PycharmProjects\pythonProject\main.py&quot;, line 35, in new_part
       np = [self.part_name.text, self.serial_number.text, self.on_hand_cnt.text, self.min_needed.text]
   AttributeError: 'NoneType' object has no attribute 'text'
  </code></pre>
  <p>Edit:</p>
  <p>Here is how I implemented Oussama's answer in order to get this to work, all I had to do was update the AddPartWindow class:</p>
  <pre><code>class AddPartWindow(Screen):
      def __init__(self, **kwargs):
          super().__init__(**kwargs)
          self.np = []
  
      def submit(self):
          self.part_name = self.ids.part_name.text
          self.serial_number = self.ids.serial_number.text
          self.on_hand_cnt = self.ids.on_hand_cnt.text
          self.min_needed = self.ids.min_needed.text
  
          self.np = [self.part_name, self.serial_number, self.on_hand_cnt, self.min_needed]
          iQue_sheet.append_row(self.np)
  </code></pre>
  <p>I also made this change in order to append to sheet 3 of the workbook instead of sheet 1:</p>
  <pre><code>iQue_sheet = client.open(&quot;InventoryBackend&quot;).get_worksheet(2)
  
  </code></pre>