Search code examples
javaandroiddynamicviewtiming

Android - Dynamic View Timing Issue?


I'm an Android newbie developer and having an issue with a tiny Android app I'm developing which I suspect is related to timing of dynamic view creation. It's a little scorekeeper app and it has dynamically generated player buttons and text fields, defined in a Player class. Here's an excerpt of how I'm doing this:

    public Player(String name, int score, int number, boolean enabled, RelativeLayout mainScreen, List<Button> presetButtonList, Editor editor, Context context) {
            super();
            this.name = name;
            this.score = score;
            this.number = number;
            this.enabled = enabled;
            this.editor = editor;

            // Get the lowest child on the mainScreen
            Integer count = mainScreen.getChildCount(), lowestChildId = null;
            Float lowestChildBottom = null;
            for (int i = 0; i < count; i++) {
                View child = mainScreen.getChildAt(i);
                if ((lowestChildId == null  || child.getY() > lowestChildBottom) && 
                        !presetButtonList.contains(child)) {
                    lowestChildId = child.getId();
                    lowestChildBottom = child.getY();
                }
            }

            playerNameText = (EditText) setTextViewDefaults(new EditText(context), name);
            playerNameText.setSingleLine();
    //      playerNameText.setMaxWidth(mainScreen.getWidth());
    //      playerNameText.setEllipsize(TextUtils.TruncateAt.END);  //TODO: Prevent names which are too long for screen
            playerNameText.addTextChangedListener(new TextWatcher() {

                  public void afterTextChanged(Editable changedText) {
                      setName(changedText.toString());
                      updateScreen();
                  }

                  public void beforeTextChanged(CharSequence changedText, int start, int count, int after) {}

                  public void onTextChanged(CharSequence changedText, int start, int before, int count) {}
               });
            RLParams = new RelativeLayout.LayoutParams(
                    RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
            if (lowestChildId != null) {
                RLParams.addRule(RelativeLayout.BELOW, lowestChildId);
            } else {
                RLParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
                RLParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
            }
            RLParams.setMargins(0, 10, 0, 10);      
            mainScreen.addView(playerNameText, RLParams);

the class further defines buttons and etc in a similar fashion, aligning tops with the name text view. I call this when a user clicks a button to add a player, and it works fine, displaying each player below the first on down the screen. The problem comes in when I'm loading a bunch of saved players at the start. Here's where i load the players:

public void loadData() {
        RelativeLayout mainScreen = (RelativeLayout)findViewById(R.id.relativeLayout);
        playerCount = savedData.getInt("playerCount", playerCount);
        Player player;
        String name;
        playerList.clear();
        for (int i=1; i<=playerCount; i++) {
            name = savedData.getString("name" + i, null);
            if (name != null) {
                Log.v("name", name);
                player = new Player(name, savedData.getInt("score" + i, 0), i, savedData.getBoolean("enabled" + i, false), mainScreen, presetButtonList, editor, this);             
                playerList.add(player);
                player.updateScreen();
            }
        }
        updateScreen();
    }

Finally, I call the loadData() method when the app starts, here:

@Override
protected void onCreate(Bundle savedInstanceState) {        
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    savedData = this.getPreferences(Context.MODE_PRIVATE);
    editor = savedData.edit();

    presetButtonList.add((Button)findViewById(R.id.newGame));
    presetButtonList.add((Button)findViewById(R.id.addPlayer));
    presetButtonList.add((Button)findViewById(R.id.removePlayer));

    loadData();     
}

The result? When there are more than two players to load, all the players get loaded to the same spot, on top of Player 2.

I suspect somehow that the players are all being generated at the same time and thus all believing that the lowest player view is Player 1, and not checking each other. I've tried triggering the load later than onCreate() and it still happens. I also tried adding a 3 second sleep within the for loop of loadData() after each player loads to see if that helped, but no luck.

Am I making bad assumptions? What am I doing wrong and how might I fix it?


Solution

  • It was a timing issue after all. I needed to wait for the main screen to load before I could add views to it. Unfortunately, it would never load until all activity on the UI thread was finished.

    Thus, I needed threading. I ended up putting loadData() in a thread, like this:

    new Thread(new loadDataAsync(this)).start();
    
    
    
    class loadDataAsync implements Runnable {
    
        MainActivity mainActivity;
    
        public loadDataAsync(MainActivity mainActivity) {
            this.mainActivity = mainActivity;
        }
    
        @Override
        public void run() {
            try {
                Thread.sleep(700);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            loadData(mainActivity);         
        }
    }
    

    Then I created the players back on the UI thread after the initial sleep to give time for the UI to load.

    runOnUiThread(new Runnable() {
                    Player player;
                    RelativeLayout mainScreen = (RelativeLayout)findViewById(R.id.relativeLayout);
                    @Override
                    public void run() {
                        player = new Player(savedData.getString("name" + playerNum, null), savedData.getInt("score" + playerNum, 0), playerNum, savedData.getBoolean("enabled" + playerNum, false), mainScreen, presetButtonList, editor, mainActivity);    
                        playerList.add(player);
                        player.updateScreen();
                        mainScreen.invalidate();
                    }
                });
    

    Not the most elegant solution, but it works for now.