Search code examples
pythonkivytogglebutton

Reloading a Kivy Screen with a Button Push


I am currently trying to create an application that compares two user's playlist to each other using Spotipy and Kivy. My issue is when I try to switch pages in kivy, the page that I switch to doesn't have any of the elements that it is supposed to be loaded with from a previous function.

My main.py file builds the user login page (just asks for two usernames currently), then calls a validate function with a submit button. The validate function calls spotify's api to get each an OAuth token and then calls the PlaylistScreen class's function "show_playlists" which searches the passed in users' playlists and is supposed to add them to the PlaylistPage's floatlayout. Then the submit button from the login page should update the page to be the PlaylistPage that has the two grids full of togglebuttons with a submit button.

For some reason, when you press the button the API is called and all of the playlists are gotten and it looks like the widgets are all populated exactly how I want them to be, but nothing is output to the screen.

Here is my main.py code (Leaving out my ClientID and ClientSecret for the OAuth):

import sys
from statistics import mean

import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.config import Config
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window

from functools import partial

import spotipy
from spotipy.oauth2 import SpotifyOAuth
import spotipy.util as util

scope = 'user-library-read'

# Setting resizeable window
Config.set('graphics', 'resizable', True)

# Class for storing song data for each track.
class SongData:
    def __init__(self, name, dance, energy, key, loudness, speechiness, acousticness, instrumentalness, liveness,
                 valence, tempo):
        self.name = name
        self.dance = dance
        self.energy = energy
        self.key = key
        self.loudness = loudness
        self.speechiness = speechiness
        self.acousticness = acousticness
        self.instrumentalness = instrumentalness
        self.liveness = liveness
        self.valence = valence
        self.tempo = tempo


# Class for storing playlists.
class Playlist:
    def __init__(self, name, songlist):
        self.name = name
        self.songList = songlist


# Class to show login Page
class LoginPage(Screen):
    user = ObjectProperty(None)

    @staticmethod
    def validate(user1, user2):
        auth_manager = SpotifyOAuth(scope=scope, client_id='#your spotify client ID#',
                                    client_secret='#your spotify client secret#',
                                    redirect_uri='http://localhost:8080/')
        sp = spotipy.Spotify(auth_manager=auth_manager)

        PlaylistPage().show_playlists(sp, user1, user2)

        # 37i9dQZF1EVHGWrwldPRtj
        # 48s1ymkG8LOVAxoXV20Uyx
        pass


class PlaylistPage(Screen):
    sp = ObjectProperty(None)
    screenmanager = ObjectProperty(None)
    selected_playlist_1 = ObjectProperty(None)
    selected_playlist_2 = ObjectProperty(None)

    def __init__(self, **kw):
        super().__init__(**kw)

    def show_playlists(self, sp, user1, user2):
        outer_grid = GridLayout(cols=2)
        user1_all_playlists = sp.user_playlists(user1)
        user2_all_playlists = sp.user_playlists(user2)

        user1_grid = GridLayout(cols=1)
        user2_grid = GridLayout(cols=1)
        user_list = []

        b1_list = []
        b2_list = []

        for cur_list in enumerate(user1_all_playlists['items']):
            b = ToggleButton(text=cur_list[1]['name'], group='user1')
            b.id = cur_list[0]
            b1_list.append(b)
            user_list.append(cur_list)
            user1_grid.add_widget(b)
            print(cur_list[1]['name'])
            print()

        outer_grid.add_widget(user1_grid)

        if user2_all_playlists:
            for cur_list in enumerate(user2_all_playlists['items']):
                b = ToggleButton(text=cur_list[1]['name'], group='user2')
                b.id = cur_list[0]
                b2_list.append(b)
                user_list.append(cur_list)
                user2_grid.add_widget(b)
                print(cur_list[1]['name'])
                print()

            outer_grid.add_widget(user2_grid)

        self.ids.playlist_layout.add_widget(outer_grid)

        submit = Button(text="Compare Playlists")
        submit.bind(on_press=self.compute_avg)

        self.ids.playlist_layout.add_widget(submit)
        self.ids.playlist_layout.do_layout()

    def compute_avg(self):

        result = self.sp.playlist_items(self.selected_playlist_1[1]['id'])

        track_list1 = []

        for track in result['items']:
            track_uri = track["track"]["uri"]
            track_name = track["track"]["name"]
            track_data = self.sp.audio_features(track_uri)[0]
            track_dance = track_data["danceability"]
            track_energy = track_data["energy"]
            track_key = track_data["key"]
            track_loudness = track_data["loudness"]
            track_speechiness = track_data["speechiness"]
            track_acousticness = track_data["acousticness"]
            track_instrumentalness = track_data["instrumentalness"]
            track_liveness = track_data["liveness"]
            track_valence = track_data["valence"]
            track_tempo = track_data["tempo"]

            this_track = SongData(track_name, track_dance, track_energy, track_key, track_loudness,
                                  track_speechiness, track_acousticness, track_instrumentalness,
                                  track_liveness, track_valence, track_tempo)

            track_list1.append(this_track)

        result = self.sp.playlist_items(self.selected_playlist_1[1]['id'])

        track_list2 = []

        for track in result['items']:
            track_uri = track["track"]["uri"]
            track_name = track["track"]["name"]
            track_data = self.sp.audio_features(track_uri)[0]
            track_dance = track_data["danceability"]
            track_energy = track_data["energy"]
            track_key = track_data["key"]
            track_loudness = track_data["loudness"]
            track_speechiness = track_data["speechiness"]
            track_acousticness = track_data["acousticness"]
            track_instrumentalness = track_data["instrumentalness"]
            track_liveness = track_data["liveness"]
            track_valence = track_data["valence"]
            track_tempo = track_data["tempo"]

            this_track = SongData(track_name, track_dance, track_energy, track_key, track_loudness,
                                  track_speechiness, track_acousticness, track_instrumentalness,
                                  track_liveness, track_valence, track_tempo)

            track_list2.append(this_track)

        p1_name = self.sp.playlist(self.ids.playlist_1.text)['name']
        p2_name = self.sp.playlist(self.ids.playlist_2.text)['name']

        p1 = Playlist(p1_name, track_list1)
        p2 = Playlist(p2_name, track_list2)

        avg_list1 = SongData(p1.name, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
        for i, song in enumerate(p1.songList, start=1):
            avg_list1.dance += song.dance
            avg_list1.energy += song.energy
            avg_list1.key += song.key
            avg_list1.loudness += song.loudness
            avg_list1.speechiness += song.speechiness
            avg_list1.acousticness += song.acousticness
            avg_list1.instrumentalness += song.instrumentalness
            avg_list1.liveness += song.liveness
            avg_list1.valence += song.valence
            avg_list1.tempo += song.tempo

        avg_list2 = SongData(p2.name, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
        for i, song in enumerate(p2.songList, start=1):
            avg_list2.dance += song.dance
            avg_list2.energy += song.energy
            avg_list2.key += song.key
            avg_list2.loudness += song.loudness
            avg_list2.speechiness += song.speechiness
            avg_list2.acousticness += song.acousticness
            avg_list2.instrumentalness += song.instrumentalness
            avg_list2.liveness += song.liveness
            avg_list2.valence += song.valence
            avg_list2.tempo += song.tempo

        self.clear_widgets()
        self.canvas.clear()

        outer_grid = GridLayout(cols=3)
        grid1 = GridLayout(cols=1)
        grid2 = GridLayout(cols=1)

        n_text = f"Name: {avg_list1.name}"
        n = Button(disabled=True, text=n_text)
        grid1.add_widget(n)

        d_text = f"Danceability Avg: {avg_list1.dance / len(p1.songList):,.4f}"
        d = Button(disabled=True, text=d_text)
        grid1.add_widget(d)

        e_text = f'Energy Avg: {avg_list1.energy / len(p1.songList):,.4f}'
        e = Button(disabled=True, text=e_text)
        grid1.add_widget(e)

        k_text = f"Key Avg: {avg_list1.key / len(p1.songList):,.4f}"
        k = Button(disabled=True, text=k_text)
        grid1.add_widget(k)

        loud_text = f"Loudness Avg: {avg_list1.loudness / len(p1.songList):,.4f}"
        loud = Button(disabled=True, text=loud_text)
        grid1.add_widget(loud)

        s_text = f"Speechiness Avg: {avg_list1.speechiness / len(p1.songList):,.4f}"
        s = Button(disabled=True, text=s_text)
        grid1.add_widget(s)

        a_text = f"Acousticness Avg: {avg_list1.acousticness / len(p1.songList):,.4f}"
        a = Button(disabled=True, text=a_text)
        grid1.add_widget(a)

        i_text = f"Instrumentalness Avg: {avg_list1.instrumentalness / len(p1.songList):,.4f}"
        i = Button(disabled=True, text=i_text)
        grid1.add_widget(i)

        live_text = f"Liveness Avg: {avg_list1.liveness / len(p1.songList):,.4f}"
        live = Button(disabled=True, text=live_text)
        grid1.add_widget(live)

        t_text = f"Tempo Avg: {avg_list1.tempo / len(p1.songList):,.4f}"
        t = Button(disabled=True, text=t_text)
        grid1.add_widget(t)

#########################################################################################

        n_text = f"Name: {avg_list2.name}"
        n = Button(disabled=True, text=n_text)
        grid2.add_widget(n)

        d_text = f"Danceability Avg: {avg_list2.dance / len(p2.songList):,.4f}"
        d = Button(disabled=True, text=d_text)
        grid2.add_widget(d)

        e_text = f'Energy Avg: {avg_list2.energy / len(p2.songList):,.4f}'
        e = Button(disabled=True, text=e_text)
        grid2.add_widget(e)

        k_text = f"Key Avg: {avg_list2.key / len(p2.songList):,.4f}"
        k = Button(disabled=True, text=k_text)
        grid2.add_widget(k)

        loud_text = f"Loudness Avg: {avg_list2.loudness / len(p2.songList):,.4f}"
        loud = Button(disabled=True, text=loud_text)
        grid2.add_widget(loud)

        s_text = f"Speechiness Avg: {avg_list2.speechiness / len(p2.songList):,.4f}"
        s = Button(disabled=True, text=s_text)
        grid2.add_widget(s)

        a_text = f"Acousticness Avg: {avg_list2.acousticness / len(p2.songList):,.4f}"
        a = Button(disabled=True, text=a_text)
        grid2.add_widget(a)

        i_text = f"Instrumentalness Avg: {avg_list2.instrumentalness / len(p2.songList):,.4f}"
        i = Button(disabled=True, text=i_text)
        grid2.add_widget(i)

        live_text = f"Liveness Avg: {avg_list2.liveness / len(p2.songList):,.4f}"
        live = Button(disabled=True, text=live_text)
        grid2.add_widget(live)

        t_text = f"Tempo Avg: {avg_list2.tempo / len(p2.songList):,.4f}"
        t = Button(disabled=True, text=t_text)
        grid2.add_widget(t)

        outer_grid.add_widget(grid1)
        outer_grid.add_widget(grid2)

        def div(v1, v2):
            if v1 < v2:
                return v1/v2
            return v2/v1

        dance_comp = div(avg_list1.dance, avg_list2.dance)
        energy_comp = div(avg_list1.energy, avg_list2.energy)
        key_comp = div(avg_list1.key, avg_list2.key)
        loudness_comp = div(avg_list1.loudness, avg_list2.loudness)
        speechiness_comp = div(avg_list1.speechiness, avg_list2.speechiness)
        acousticness_comp = div(avg_list1.acousticness, avg_list2.acousticness)
        instrumentalness_comp = div(avg_list1.instrumentalness, avg_list2.instrumentalness)
        liveness_comp = div(avg_list1.liveness, avg_list2.liveness)
        valence_comp = div(avg_list1.valence, avg_list2.valence)
        tempo_comp = div(avg_list1.tempo, avg_list2.tempo)

        comp_list = [dance_comp, energy_comp, key_comp, loudness_comp, speechiness_comp, acousticness_comp,
                     instrumentalness_comp, liveness_comp, valence_comp, tempo_comp]

        comp_string = f"Compatibility: {(mean(comp_list))*100:2f}%"
        comp_b = Button(disabled=True, text=comp_string, size=(20, 50))

        outer_grid.add_widget(comp_b)

        self.add_widget(outer_grid)


# Class for managing all windows in the application
class WindowManager(ScreenManager):
    pass


# KV File
kv = Builder.load_file('PTStyles.kv')

Window.clearcolor = (0, 0.5, 0.05, 10)
Window.size = (600, 600)

sm = WindowManager()

sm.add_widget(LoginPage(name='login'))
sm.add_widget(PlaylistPage(name='playlists'))


# Class to run application.
class PTApp(App):
    # Code to build the application's view.
    def build(self):
        return sm


# Run the App
if __name__ == "__main__":
    PTApp().run()

And here is my .kv file:

#:import SlideTransition kivy.uix.screenmanager.SlideTransition
#:import SwapTransition kivy.uix.screenmanager.SwapTransition
#:import WipeTransition kivy.uix.screenmanager.WipeTransition
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
#:import RiseInTransition kivy.uix.screenmanager.RiseInTransition
#:import FallOutTransition kivy.uix.screenmanager.FallOutTransition
#:import NoTransition kivy.uix.screenmanager.NoTransition

WindowManager:
    LoginPage:
    PlaylistPage:

<LoginPage>:
    username1 : user_1
    username2 : user_2

    FloatLayout:
        id: login_layout
        size: root.width, root.height
        cols: 1

        Label:
            id: u1_l
            name: 'u1'
            text: "User 1:"
            size_hint: 0.2, 0.1
            pos_hint: {"x":0.25, "top":0.8}

        TextInput:
            id: user_1
            name: 'user1'
            text: 'joewilkinson00'
            hint_text: 'User 1...'
            size_hint: 0.5, 0.075
            pos_hint: {"x":0.25, "top": 0.7}

        Label:
            id: u2_l
            name: 'u2'
            text: "User 2: "
            size_hint: 0.2, 0.1
            pos_hint: {"x":0.25, "top":0.6}

        TextInput:
            id: user_2
            name: 'user2'
            text: 'kenzieloupete'
            hint_text: 'User 2...'
            size_hint: 0.5, 0.075
            pos_hint: {"x":0.25, "top": 0.5}

        Button:
            id: submit_button
            name: 'submit'
            text: 'Select Users'
            size_hint: 0.5, 0.1
            pos_hint: {"x":0.25, "top" : 0.4}
            on_release:
                root.validate(user_1.text, user_2.text)
                root.manager.transition.direction = 'left'
                root.manager.current = 'playlists'

<PlaylistPage>:
    name: 'playlists'
    FloatLayout:
        id: playlist_layout
        name: 'playlist_layout'
        size: root.width, root.height
        cols: 1

Thank you in advance if anyone is able to help me with this problem. I have been trying to figure out a way though this for days, to the ponit where I have considered switching to a technology where it is easier to switch between screens.

P.S.: If anyone has a good algorithm for looping through two lists and taking the averages between individual nodes in each list, that would be very helpful as I know my method right now is unlrealiable and a lot of lines of code.


Solution

  • Replace:

    @staticmethod
    def validate(user1, user2):
    

    with:

    def validate(self, user1, user2):
    

    and:

    PlaylistPage().show_playlists(sp, user1, user2)
    

    with:

    self.manager.get_screen('playlists').show_playlists(sp, user1, user2)
    

    Then within PTStyles.kv file replace:

    <PlaylistPage>:
        name: 'playlists'
        FloatLayout:
    

    with:

    <PlaylistPage>:
        name: 'playlists'
        GridLayout:
    

    When kivy loads kv file it makes objects from classes defined within kv, so there are objects created for WindowManager, LoginPage and PlaylistPage.

    Your mistake was that you created brand new PlaylistPage object instance and called it's method within line: PlaylistPage().show_playlists(sp, user1, user2) It works but this way you created another PlaylistPage object, which was not known to WindowManager. Your widgets were added, but NOT to already existing PlaylistPage object instance, created by Kivy itself. You have to retrieve object from WindowManager, then call your show_playlists method on it. It will add your widgets from the code to proper screen object. I also replaced FloatLayout with GridLayout, because last added widget (submit button) covered all screen area , making your GridLayouts not visible at all. Enjoy!