Related
I'm building a chat platform, where I'm reading my list of messages from Local Room DB (ChatModel). I need to add date separators between these messages. I've to use multiple view holders and thus created a sealed class for differentiating items
sealed class ChatUiModel {
data class ChatItem(val message: ChatModel) : ChatUiModel()
data class DateSeparatorItem(val time: String) : ChatUiModel()
}
I require to convert the list with date separate items in between 2 models of the list, I'm not proficient with Collection functions in kotlin and confused between map/flatmap etc.
.observe(viewLifecycleOwner) { messages ->
messages.map {
// if item prev.date < item next.date
ChatUiModel.DateSeparatorItem(it.date.toReadableTime())
ChatUiModel.ChatItem(it)
}
chatAdapter.submitList(messages)
}
Reached to this
val items = mutableListOf<ChatUiModel>()
val data = messages.listIterator()
for (item in data) {
if (data.hasPrevious())
if (data.previous().time < item.time)
items.add(ChatUiModel.DateSeparatorItem(item.time))
items.add(ChatUiModel.ChatItem(item))
}
Timber.i("CHAT = $items")
An easy way to prepare the list can be:
messages
.groupBy { it.date }
.map { (date, chatModels) ->
listOf(DateSeparatorItem(date)) + chatModels.map { ChatItem(it) }
}
.flatten()
try it yourself
Here we first group all the messages by their data to get a Map<Long, List<ChatModel>. Then we map each entry of the map to a new list containing the DateSeparator and the ChatItems for that date. Finally, we flatten the entire list to get the desired List<ChatUiModel>.
In the code that I linked, I have used Long for the date. If you have a String you can easily interconvert them using java.time APIs.
If your messages list is not sorted initially, add a sortedBy function before groupBy to sort it first.
(this ended up long but I thought you'd like an explanation of what's going on - you can just skip to the end for the solutions if you want)
Ok, so this is a little tricky if you're not familiar with all the utility functions and general functional manipulation - what you're basically doing is transforming incoming messages into ChatItems, but you also want to compare each message to the previous one, and output a DateSeparatorItem first where necessary, right?
A straight map isn't going to work - that just transforms each item into another item (it's mapping one value to another), and sometimes you want to transform one item into two (a date item and a chat item).
You could map each message item into a list, and make that contain either a chat item, or a date+chat. So that would give you a list of lists, which you could then flatten so you just get all those items in order, in a single list. That's basically what flatmap does!
So now you need to be able to compare multiple messages, so you can check the dates. Kotlin has this windowed function that acts like a sliding view across your collection, so it can transform [1, 2, 3, 4] into [[1, 2], [2, 3], [3, 4]], and then you can work on those groups. There's a more convenient zipWithNext function that only produces Pairs instead of arbitrarily sized Lists - i.e. [(1, 2), (2, 3), (3, 4)], but windowed has a useful option - partialWindows allows that window to keep moving to the end of the list, even as it runs out of items to fill the full window:
listOf(1, 2, 3, 4).windowed(size=3, partialWindows=true).run(::println)
>> [[1, 2, 3], [2, 3, 4], [3, 4], [4]]
If we do this for a window of size 2, we get every original message, and also the one following it if there is one (zipWithNext will stop when it runs out of complete pairs):
listOf(1, 2, 3, 4).windowed(size=2, partialWindows=true).run(::println)
>> [[1, 2], [2, 3], [3, 4], [4]]
We can use this!
Your logic right now is taking a message and comparing it to the previous one to see if a date needs inserting before the chat item - I'd suggest flipping that around, and inserting a date after the current item by checking the next item's timestamp. That's because windowed is giving you each item along with the next one, so you don't get to look at the previous one.
We're working with a list here, and we need to compare the first item to the second one (checking if there even is one), but we can be a little bit cheeky and just compare list.first() with list.last(). We know there's gonna be either one or two items - and if there's only one item in the list (i.e. it's the last message) then we're comparing it with itself, and since we're only adding the date item if the timestamps are different... well they won't be if it's the same item! So there won't be any rogue date items added at the end. Probably worth documenting the code if you do that since it might not be clear - you can write some more explicit logic if you want.
Here's a few ways to do the final thing:
Kotlin Playground example
data class Message(val text: String, val time: Int)
val messages = listOf(
Message("hey", 1),
Message("u up", 1),
Message("lol", 3),
Message("wow", 10)
)
fun withMutableList() {
messages.windowed(size=2, partialWindows=true)
// or map followed by flatten()
.flatMap { items ->
val current = items.first()
val next = items.last()
// creating a mutable list with the chat item, optionally adding a date
mutableListOf<ChatUiModel>(ChatItem(current)).apply {
if (next.time > current.time) add(DateItem(next.time))
}
}
.forEach(::println)
}
fun withNulls() {
messages.windowed(size=2, partialWindows=true)
.flatMap { items ->
val current = items.first()
val next = items.last()
// either adding a date or a null, nulls get removed later
listOf(
ChatItem(current),
if (next.time > current.time) DateItem(next.time) else null
)
}
.filterNotNull()
.forEach(::println)
}
fun withSequence() {
sequence {
messages.windowed(size=2, partialWindows=true)
.forEach { items ->
val current = items.first()
val next = items.last()
// just yielding a stream of items, nice and neat!
yield(ChatItem(current))
if (next.time > current.time) yield(DateItem(next.time))
}
}.forEach(::println)
}
all giving this output:
ChatItem(message=Message(text=hey, time=1))
ChatItem(message=Message(text=u up, time=1))
DateItem(time=3)
ChatItem(message=Message(text=lol, time=3))
DateItem(time=10)
ChatItem(message=Message(text=wow, time=10))
Trying to learn Android studio. And I expect your help on this.I am adding and listing data with sqlite.
for example;
id - name - value
1 - john - 100
2 - mark - 200
3 - john - 150
4 - john - 200
5 - adam - 400
what I want to do, list only names one time.
1 - john
2 - mark
3 - adam
private void showlist() {
ArrayList<DataListItems> contactList = new ArrayList<DataListItems>();
contactList.clear();
String query = "SELECT * FROM data ";
Cursor c1 = sqlHandler.selectQuery(query);
if (c1 != null && c1.getCount() != 0) {
if (c1.moveToFirst()) {
do {
DataListItems contactListItems = new DataListItems();
contactListItems.setid(c1.getString(c1
.getColumnIndex("id")));
contactListItems.setName(c1.getString(c1
.getColumnIndex("name")));
contactListItems.setValue(c1.getString(c1
.getColumnIndex("value")));
contactList.add(contactListItems);
} while (c1.moveToNext());
}
}
c1.close();
DataListAdapter contactListAdapter = new DataListAdapter(
SiteList.this, contactList);
lvCustomList.setAdapter(contactListAdapter);
}
You can use the GROUP BY name to select only one name. However, the id selected would be an arbitrary id for each group (name).
your code could use String query = "SELECT * FROM data GROUP BY name";
If you wanted the first/lowest id per name then you could use min(id) in conjunction with GROUP BY NAME.
your code could use String query = "SELECT min(id) AS id, name, value FROM data GROUP BY name";
You say that your expected result should be
1 - john
2 - mark
3 - adam
That would be more complicated (and perhaps of little use) as the id for adam is 5 not 3 (I assume that is simply a typing error).
as docs say( https://github.com/cloudant/sync-android/blob/master/doc/query.md) im trying to do a query inside a list
look:
query.put("codigo", 4);
result = q.find(query);
for (DocumentRevision revision : result) {
Log.d("QueryTest", "Test1 :" + revision.getBody().toString());
}
return:
{ "codigo":4, "companies":[
{
"id":"b9f19d88-13c0-40e3-89de-63dc787afb4c",
"name":"Filial 0 1488949817178"
},
{
"id":"f17fb098-316e-4d33-a0f7-f5719bf9d62e",
"name":"Filial 1 1488949817178"
} ], "employees":[
{
"codigo":2891,
"id":"cc54fa37-0b64-4108-869a-1303c6176ce5",
"name":"Employee 0 79ed4"
},
{
"codigo":4642,
"id":"19b76bbc-82c7-4295-a385-82ac2d892458",
"name":"Employee 1 e1102"
} ], "id":"ef2d0ebf-50b9-4cd0-9aaf-5389bccaaa56", "nome":"Random 4" }
so its ok
but this query doesnt work:
query.put("employees.codigo", 2891);
first return:
QuerySqlTranslator: No single index contains all of
[{employees.codigo={$eq=2891}}]; add index for these fields to query
efficiently
after created the index:
Index i = q.createJsonIndex(Arrays.<FieldSort>asList(new FieldSort("employees.codigo")), null);
return
QueryExecutor: Projection fields array is empty, disabling project for
this query
so i created the arraylist of fields to filter
List<String> fields = Arrays.asList("codigo");
result = q.find(query, 0 , 0 , fields, null);
nothing returned
adding more one field to filter:
query.put("codigo",4)
query.put("employees.codigo", 2891);
returned: Complain about index
Created another index:
i = q.createJsonIndex(Arrays.<FieldSort>asList(new FieldSort("employees.codigo"), new FieldSort("codigo")), null);
returned: Nothing
whats is wrong?
How can i get document by child and how can i get ONLY the children?
thanks
I think this is unsupported, see the doc on unsupported features, specifically the highlighted entry:
Arrays
Dotted notation to index or query sub-documents in arrays.
Querying for exact array match, { field: [ 1, 3, 7 ] }.
Querying to match a specific array element using dotted notation, { field.0: 1 }.
Querying using $all.
Querying using $elemMatch.
How to get the last row with value in the new Google Sheets API v4 ?
i use this to get a range from a sheet:
mService.spreadsheets().values().get("ID_SHEET", "Sheet1!A2:B50").execute();
how to detect the last row with value in the sheet ?
You can set the range to "A2:D" and this would fetch as far as the last data row in your sheet.
I managed to get it by counting the total rows from current Sheets.
Then append the data to the next row.
rowcount = this.mService.spreadsheets().values().get(spreadsheetId, range).execute().getValues().size()
Rather than retrieving all the rows into memory just to get the values in the last row, you can use the append API to append an empty table to the end of the sheet, and then parse the range that comes back in the response. You can then use the index of the last row to request just the data you want.
This example is in Python:
#empty table
table = {
'majorDimension': 'ROWS',
'values': []
}
# append the empty table
request = service.spreadsheets().values().append(
spreadsheetId=SPREADSHEET_ID,
range=RANGE_NAME,
valueInputOption='USER_ENTERED',
insertDataOption='INSERT_ROWS',
body=table)
result = request.execute()
# get last row index
p = re.compile('^.*![A-Z]+\d+:[A-Z]+(\d+)$')
match = p.match(result['tableRange'])
lastrow = match.group(1)
# lookup the data on the last row
result = service.spreadsheets().values().get(
spreadsheetId=SPREADSHEET_ID,
range=f'Sheetname!A{lastrow}:ZZ{lastrow}'
).execute()
print(result)
😢 Google Sheets API v4 does not have a response that help you to get the index of the last written row in a sheet (row that all cells below it are empty). Sadly, you'll have to workaround and fetch all sheet rows' into memory (I urge you to comment if I'm mistaken)
Example:
spreadsheet_id = '1TfWKWaWypbq7wc4gbe2eavRBjzuOcpAD028CH4esgKw'
range = 'Sheet1!A:Z'
rows = service.spreadsheets().values().get(spreadsheetId=spreadsheet_id, range=range).execute().get('values', [])
last_row = rows[-1] if rows else None
last_row_id = len(rows)
print(last_row_id, last_row)
Output:
13 ['this', 'is ', 'my', 'last', 'row']
💡 If you wish to append more rows to the last row, see this
You don't need to. Set a huge range (for example A2:D5000) to guarantee that all your rows will be located in it. I don't know if it has some further impact, may be increased memory consumption or something, but for now it's OK.
private List<String> getDataFromApi() throws IOException {
String spreadsheetId = "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms";
String range = "A2:D5000";
List<String> results = new ArrayList<String>();
ValueRange response = this.mService.spreadsheets().values()
.get(spreadsheetId, range)
.execute();
List<List<Object>> values = response.getValues();
if (values != null) {
results.add("Name, Major");
for (List row : values) {
results.add(row.get(0) + ", " + row.get(3));
}
}
return results;
}
Look at the loop for (List row : values). If you have two rows in your table you will get two elements in values list.
Have a cell somewhere that doesn't interfere with your datarange with =COUNTA(A:A) formula and get that value.
In your case
=MAX(COUNTA(A:A50),COUNTA(B:B50))
?
If there could be empty cells inbetween the formula would be a little more tricky but I believe it saves you some memories.
2022 Update
I I don’t know if this will be relevant for someone in 2022, but now you can do it differently.
You can just set next value as range :
const column = "A"
const startIndex = 2
const range = column + startIndex + ":" + column
In resolve you get all data in column and range with last index.
I tested it on js and php
Following Mark B's answer, I created a function that performs a dummy append and then extracts the last row info from the dummy append's response.
def get_last_row_with_data(service, value_input_option="USER_ENTERED"):
last_row_with_data = '1'
try:
dummy_request_append = service.spreadsheets().values().append(
spreadsheetId='<spreadsheet id>',
range="{0}!A:{1}".format('Tab Name', 'ZZZ'),
valueInputOption='USER_ENTERED',
includeValuesInResponse=True,
responseValueRenderOption='UNFORMATTED_VALUE',
body={
"values": [['']]
}
).execute()
a1_range = dummy_request_append.get('updates', {}).get('updatedRange', 'dummy_tab!a1')
bottom_right_range = a1_range.split('!')[1]
number_chars = [i for i in list(bottom_right_range) if i.isdigit()]
last_row_with_data = ''.join(number_chars)
except Exception as e:
last_row_with_data = '1'
return last_row_with_data
I'm not sure why I'm getting this error. I'm fairly new to kivy and the error might be obvious to those more experience with kivy so please impart your knowledge on to me. I'm assuming that I'm not using the function call properly in the kv language or that I didn't define the functions in the right place. If that is the case can anyone give a few pointers how to go about doing that properly.
BTW I know that some of the libraries don't have recipes for python-for-android as of the moment, I'll get to that later. I also know that I'll have to find a workaround for matplotlib/use some other method for graphing once I'm ready to port to my device.
here's my code for main.py:
from kivy.app import App
from kivy.uix.scatter import Scatter
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.gridlayout import GridLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.core.window import Window
from kivy.uix.image import Image
from kivy.graphics import BorderImage
from kivy.graphics import Color, Rectangle, Line
import random
import matplotlib.pyplot as plt
import pandas as pd
#import pylab as pl
import requests
import openpyxl as xl
#from operator import itemgetter
from collections import Counter
from lxml import html
import numpy as np
class HomeScreen(Screen):
pass
class LotterySelcetionScreen(Screen):
pass
class TableScreen(Screen):
pass
class TutorialApp(App):
screen_manager = None
def build(self):
#Window.clearcolor = (1,1,1,1)
self.screen_manager = ScreenManager()
self.screen_manager.add_widget(HomeScreen(name='home'))
self.screen_manager.add_widget(LotterySelcetionScreen(name='lottery_selection'))
self.screen_manager.add_widget(TableScreen(name='table'))
#function to load the table form the excel file corresponding to the passed sheet name
def loadTable(self, sheetName):
lotteryData = pd.ExcelFile("Lottery databases.xlsx") #grabs and loads the file into memory
df = lotteryData.parse(sheetName) #loads the data table form the corresponding sheetName into the df data frame
return df
#function to display the table
def showTable(self, table):
#get the number of rows the table has
no_of_rows = len(table.index)
#display the table
return table.head(no_of_rows)
#function to display pie charts of a specific column within the database
#table is the database that the function will be working with
#and column is a numberical vaule of which column to get the data from
def printPieChart(self, table, column):
if column == 6:
columnList = table.iloc[:, -1:].values.T.ravel()
else:
columnList = table.iloc[:, (column - 7): (column - 6)].values.T.ravel()
countedList = Counter(columnList)
#set up the size of the pie chart
fig = plt.figure(figsize=[10, 10])
ax = fig.add_subplot(111)
cmap = plt.prism()
#input variables for pie function
slices = [float(v) for v in countedList.values()]
colors = cmap(np.linspace(0., 1., len(slices)))
labels = [float(k) for k in countedList]
columnHeaders = list(table.columns.values)
#the pie chart
pie_wedge_collection = ax.pie(slices, colors = colors, labels = labels, labeldistance = 1.05, autopct = '%1.1f%%')
#get rid of the black outlines between the wedges and around the pie
for pie_wedge in pie_wedge_collection[0]:
pie_wedge.set_edgecolor('white')
ax.set_title(columnHeaders[column + 1])
#can't display a Legends as there's too many for plt.legends() too handle
#return pyplot.pie([float(v) for v in countedList.values()], labels = [float(k) for k in countedList])
def updateDatabase(self):
wb = xl.load_workbook("Lottery databases.xlsx") #load the workbook into memory
#list of the sheet names within the workbook
sheetnames = ["SuperLotto", "MegaMillions", "Powerball"]
days = ["Tue. ", "Wed. ", "Fri. ", "Sat. "] #days the draws on done on
#list of the webpages to use grab the new draws
webPages = ['http://www.calottery.com/play/draw-games/superlotto-plus/winning-numbers', 'http://www.calottery.com/play/draw-games/mega-millions/winning-numbers', 'http://www.calottery.com/play/draw-games/powerball/winning-numbers']
x = 3
while x != 0:
ws = wb.get_sheet_by_name(sheetnames[x-1]) # which sheet to update
rowIndex = ws.get_highest_row() # gets the highest row index in the sheet
lastCellValue = ws.cell(row = rowIndex - 1, column = 0).value #gets the last value in the first column, draw number
page = requests.get(webPages[x-1]) #grabs the webpage needed
tree = html.fromstring(page.text) #puts the webpage into a tree structure to make it easy to traverse
#get the newest draw and date from the webpage for comparasion purposes
draw_and_date = tree.xpath('//*[#id="objBody_content_0_pagecontent_0_objPastWinningNumbers_rptPast_ctl01_lblDrawDateNumber"]/text()')
#if the table is up to date, it will move on to the next table else it will update it
y = int(draw_and_date[0][-4:]) - int(lastCellValue) # checks to see how many draws are missing from the table
if y == 0:
#print("The table for " + sheetnames[x-1] + " is up to date.")
x -= 1 #decrement x by 1 to move on to the next table
else:
#while loop to check if the table needs to be updated or not, if yes it will update it
while y != 0:
#grabs the draw and date of the missing draws from the table
draw_and_date = tree.xpath('//*[#id="objBody_content_0_pagecontent_0_objPastWinningNumbers_rptPast_ctl0' + str(y) + '_lblDrawDateNumber"]/text()')
numbers = tree.xpath(".//*[#id='content']/div[3]/table/tr[" + str(y) + "]/td[2]/span/text()") #numbers
numbers = [int(x) for x in numbers] # converts the text to integers
numbers.sort() #sort the number from smallest to largest
mega = tree.xpath(".//*[#id='content']/div[3]/table/tr[" + str(y) + "]/td[3]/text()") #mega number
mega = int(mega[0]) # converts the text to integers
#write to the file
if sheetnames[x-1] == "MegaMillions":
d = 0
else:
d = 1
if int(draw_and_date[0][-4:]) % 2 == 0:
# if the draw date is even then the day is a Friday/Saturday
ws.append([int(draw_and_date[0][-4:]), (days[d+2] + draw_and_date[0][:12]), numbers[0], numbers[1], numbers[2], numbers[3], numbers[4], mega]) # print the draw date
else:
# if the draw date is odd then the day is a Tuesday/Wednesday
ws.append([int(draw_and_date[0][-4:]), (days[d] + draw_and_date[0][:12]), numbers[0], numbers[1], numbers[2], numbers[3], numbers[4], mega])
y -= 1 #decrement y by 1 to get the next missing draw
#print("Updated the " + sheetnames[x-1] + " table successfully!")
x -= 1 #decrement x by 1 to move on to the next table
wb.save("Lottery databases.xlsx") #save the workbook
#print("Saved the database Sucessfully!")
# function to get a list of the occurring numbers in each column
# 6 for the first number columns or 1 for the mega/powerball column
def getPopularList(self, table, x):
popular_list = list()
if x != 1:
while x != 0:
column_list = table.iloc[:, (0 - x): (1 - x)].values.T.ravel() # the all of the values in the column
counted_list = Counter(column_list) # counts how many time each value occurs within the column
top_five = counted_list.most_common(5) # the top five within the column
top_five.sort() # sorts the top five
popular_list = popular_list + top_five
x -= 1 # decrement x by 1
else:
column_list = table.iloc[:, (0 - x):].values.T.ravel() # the all of the values in the column
counted_list = Counter(column_list) # counts how many time each value occurs within the column
top_ten = counted_list.most_common(10) # the top five within the column
top_ten.sort() # sorts the top five
popular_list = popular_list + top_ten
#popular_list is actually a tuple of list which contains the value and how many times that value occured
#but we only want the values by themselves
popular_list_values = [y[0] for y in popular_list] #this gives us a list of the values
return popular_list_values
# Function to generate a ticket based on the popular numbers in the lottery
def generate_ticket(self, table):
firstFive = self.getPopularList(table, 6) # get the popular numbers for the first five slots
mega = self.getPopularList(table, 1) # gets the popular numbers for the mega/powerball slot
five = self.getNumbers(firstFive, 5) # gets first five numbers for the ticket
one = self.getNumbers(mega, 1) #gets the mega/powerball for the ticket
five.append(one)
return five
#function to get numbers
def getNumbers(self, numbers_list, x):
numbers = list() #empty list
rand_range_list = list(range(0, len(numbers_list))) #list of the numbers to choice from at random
if x != 1:
while x != 0:
y = random.choice(rand_range_list) #pick a number
#it's not making the list properly at the moment, FIX IT!
#numbers = numbers + numbers_list[y] #add a number to our list to return
numbers.append(numbers_list[y]) #append the number to the end of the list
rand_range_list.remove(y) #remove y from our rand_range_list to prevent any repeats
x -= 1
else:
numbers = random.choice(numbers_list) # add a number to our list to return
return numbers
# function to print the ticket based on which lottery it's from
def printTicket(self, ticket, lottery):
#small bubblesort for the first five numbers
for x in range(1, 5):
for y in range(0, 4):
if ticket[y] > ticket[y + 1]:
temp = ticket[y]
ticket[y] = ticket[y + 1]
ticket[y + 1] = temp
#print statements depending on the lottery
#print the ticket without any brackets
if lottery != "Powerball":
return str(ticket)[1:-4] + " Mega " + str(ticket[-1])
else:
return str(ticket)[1:-4] + " Powerball " + str(ticket[-1])
return self.screen_manager
if __name__ == '__main__':
TutorialApp().run()
My tutorial.kv:
<HomeScreen>:
RelativeLayout:
Image:
source: 'images/background3.jpg'
size_hint: None, None
size: self.texture_size
pos: self.pos
Button:
font_size: sp(30)
text: 'Select Lottery'
size_hint_x: None
width: self.texture_size[0]
size_hint_y: None
height: self.texture_size[1]
pos: root.width / 2 - (self.width / 2), root.height / 2
on_press: root.manager.current = 'lottery_selection'
Button:
size_hint_x: None
width: self.texture_size[0]
size_hint_y: None
height: self.texture_size[1]
pos: root.width - self.width, 0
font_size: sp(30)
text: 'Quit'
on_press: app.stop()
<LotterySelcetionScreen>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'SuperLotto'
on_press: root.manager.current = 'table'
Button:
text: 'MegaMillions'
Button:
text: 'Powerball'
Button:
text: 'Back to home'
on_press: root.manager.current = 'home'
<TableScreen>:
RelativeLayout:
Label:
id: label1
text: str(app.root.generate_ticket(app.root.loadTable('SuperLotto')))
height: 150
here's the Traceback:
Traceback (most recent call last):
File "/home/havik/workspace/kivyapp2.7/main.py", line 384, in <module>
TutorialApp().run()
File "/usr/lib/python2.7/dist-packages/kivy/app.py", line 798, in run
root = self.build()
File "/home/havik/workspace/kivyapp2.7/main.py", line 207, in build
self.screen_manager.add_widget(TableScreen(name='table'))
File "/usr/lib/python2.7/dist-packages/kivy/uix/relativelayout.py", line 255, in __init__
super(RelativeLayout, self).__init__(**kw)
File "/usr/lib/python2.7/dist-packages/kivy/uix/floatlayout.py", line 66, in __init__
super(FloatLayout, self).__init__(**kwargs)
File "/usr/lib/python2.7/dist-packages/kivy/uix/layout.py", line 66, in __init__
super(Layout, self).__init__(**kwargs)
File "/usr/lib/python2.7/dist-packages/kivy/uix/widget.py", line 271, in __init__
Builder.apply(self)
File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 1872, in apply
self._apply_rule(widget, rule, rule)
File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 2018, in _apply_rule
e), cause=tb)
kivy.lang.BuilderException: Parser: File "/home/havik/workspace/kivyapp2.7/tutorial.kv", line 45:
...
43: Label:
44: id: label1
>> 45: text: str(app.root.generate_ticket(app.root.loadTable('SuperLotto')))
46: height: 150
...
BuilderException: Parser: File "/home/havik/workspace/kivyapp2.7/tutorial.kv", line 45:
...
43: Label:
44: id: label1
>> 45: text: str(app.root.generate_ticket(app.root.loadTable('SuperLotto')))
46: height: 150
...
AttributeError: 'NoneType' object has no attribute 'generate_ticket'
File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 1649, in create_handler
return eval(value, idmap)
File "/home/havik/workspace/kivyapp2.7/tutorial.kv", line 45, in <module>
text: str(app.root.generate_ticket(app.root.loadTable('SuperLotto')))
File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 2011, in _apply_rule
value, rule, rctx['ids'])
File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 1654, in create_handler
cause=tb)
You instantiate a TableScreen in the build method, before app.root has been set, so app.root.generate_ticket doesn't exist.
One option would be to set the TableScreen content directly in the build method rather than with a kv rule.
I tried doing it that way inclement suggested but I couldn't properly define the screen in the build method, so I tried to fiddle with the code in kv language and was able to fix the problem. I feel really stupid for not realizing sooner. I just needed to remove root from str(app.root.generate_ticket(app.root.loadTable('SuperLotto'))) and that solved the problem.