0

I'm currently writing a sports app which is able to keep track of a scoreline. The user is supposed to choose a sport in an activity called "ChooseSport" because I am planning to include features specific for different sports. After his choice he is directed to an activity where he is supposed to specify the team names and finally to the activity where he can change the score by submitting user input every time a goal occurs. Depending on his choice in the first activity, both of the subsequent activitites need to be different. But they should still have many similarities.

I have therefore decided to set up everything in the following way: "ChooseSport" starts an intent to open the activity containing the EditTexts for the team names dependent on the sport, such as "FootballActivity", "HandballActivity", "WaterpoloActivity". All these activites inherit from the more generic class "SportActivity". The third activity containing the scoreline is treated similarly: there are activities such as "FootballScoreActivity", "HandballScoreActivity" and "WaterpoloScoreActivity" which all inherit from the more generic class "ScoreActivity". If you pick "football" in "ChooseSport" you are supposed to be directed to "FootballActivity" and "FootballScoreActivity" afterwards. So depending on the choice of sport not only the intent to get from "ChooseSport" to the second activity, but also the one to get from the second activity to the third one changes. I have tried to implement this behaviour using static variables in "SportActivity":

public class SportActivity extends AppCompatActivity {
    public static String titleToolbar;
    static ScoreActivity scoreActivityInstance;
    ...
    Intent intent = new Intent(this, streamActivityInstance.getClass());
    ...
    startActivity(intent);
}

 public class FootballActivity extends SportActivity {
    static {
    titleToolbar = "Football";
    scoreActivityInstance = new FootballScoreActivity();
 }
}

( I'm using .getClass() on an instance instead of .class on the class because I haven't found a way to reference the correct class in SportActivity depending on the decision in "ChooseSport".)

This seemed to work fine at first but after some testing I have spotted a serious bug:

If I open a FootballScore starting from ChooseSport, go back pushing the back button twice, open a HandballScore, go back again, and open a FootballScore again, I end up having a HandballScore! I suspect the following thing to happen: when starting the first FootballActivity, the static variables in the superclass SportActivity are overwritten, because the FootballActivity is actually initialised. Starting the first HandballActivity, the static variables of SportActivity are again overwritten, because the HandballActivity is actually initialised. Starting a FootballActivity for the second time however doesn't seem to overwrite the static superclass variables, because again a HandballScore is started so that apparently the superclass variables had still been in the "handball mode". I assume this might be because there is already an instance of FootballActivity so android does not instantiate FootballActivity again resulting in the static variables of the superclass to not change. The problem seems quite difficult to me because android is instantiating FootballActivity and HandballActivity on its own and I do not have full insight of how android is doing this. To fix the bug I have tried to call finish() after startActivity(intent) in SportActivity. I expected the activity to be destroyed since finish() is supposed to trigger a call to onDestroy(). Nevertheless, this did not fix the bug, and reading a bit further I found out that finish() just results in onDestroy() being invoked but not necessarily in the instance of the activity being destroyed (see the ticked comment here).

So I'd be very thankful for any hints how to fix my bug, in particular:

1.) Do you agree that my general approach makes sense?

2.) Even if so, can you come up with a way which does not lead to such difficulties?

3.) Do you agree on my assumptions why the bug occurs?

4.) What can I do to fix the bug?

Thanks for any well-meant piece of advice.

H.G.M.
  • 81
  • 1
  • 7
  • `Activity` in Android has its own life-cycle. Read about it. You cannot use static fields for storing state of the activities... – Usagi Miyamoto Aug 24 '17 at 08:05
  • I am aware of some implications of the activity lifecycle and I do not see in how far my approach using static fields might be an abuse of it. Still I would be willing to change my starting point, if that fixed the issue while still providing all benefits. I have been using inheritage in order to minimize duplicates in my code and the code's total length. Do you know of an alternative to avoid my issue while still providing an efficient implementation using as little code as possible? – H.G.M. Aug 24 '17 at 08:23
  • `new FootballScoreActivity()` never do this – Tim Aug 24 '17 at 09:24
  • I have read somewhere else that instantiating activities should generally be avoided, but I do not really get why. Even though android is supposed to do this work on its own, I cannot see how it could harm, if I instantiated some objects by myself. Can you explain the problems which might arise? – H.G.M. Aug 24 '17 at 13:08

1 Answers1

0

When an Activity is created, certain events are called in a certain order. See: The Activity Lifecycle

Your static fields is assigned with a static initializer, that is being run at the time the class loader first loads the class. Thus if you selects a FootballScore, then a HandballScore, then again a FootballScore, the first 2 times the class is loaded, and the assignment is done, but the third time, the class is already loaded, so no assignment is done.

Such initialization should be done in the Activity's lifecycle events, EG the "current activity" should be assigned in the onResume() event handler.

Also remember, that these classes can be unloaded at any time, so the static field can loose its value, too.

There are permanent storage options, like config files, system propoerties, etc. for persistent data storage.

EDIT

public class SportActivity extends AppCompatActivity {
   protected String titleToolbar;

   @Overriden
   public void onCreate(Bundle context) {
      ...
   }
}

public class FootballActivity extends SportActivity {
   @Overriden
   public void onCreate(Bundle context) {
      titleToolbar = "Football";
      super.onCreate(context);
      ...
   }
}
Usagi Miyamoto
  • 6,196
  • 1
  • 19
  • 33
  • First off thanks for your help. I appreciate it. However, I still do not know how to fix my issue. I get from your comment that my basic implementation is already problematic. Your argumentation why my way of handling things is fated to fail is basically similar to what I had already suspected in the original post. So apparently there is not really a way of "saving" my code and I need to overthink the concept. Or can I manually influence when classes are loaded to explicitly enforce a load whenever a new score is created? – H.G.M. Aug 24 '17 at 13:01
  • Talking about alternatives and dropping the concept of static fields: I think saving data is not really what I am concerned about. I need to have activities which show many similarities but also some differences. The differences are what I declared static in order to overwrite the corresponding variables in the subclasses. I have been trying to use inheritance to avoid code duplicates. Consider the following approach I just came up with which might be an alternative: I drop the subclasses and always open SportActivity from ChooseSport. – H.G.M. Aug 24 '17 at 13:03
  • To know which sport the user has chosen I put a String as extra to the intent so I can still retrieve this piece of information in SportActivity. To get the information about what to do for a particular sport (e.g. which text to write in the Toolbar) I could then implement an interface containing the information about all sports and - depending on the String from the intent - use the information about the sport in question as variables in my SportActivity. This would involve some if statements but avoid duplicating all the code in SportActivity. What do you think? – H.G.M. Aug 24 '17 at 13:03
  • You can use inheritance, just remember, that instances of the child classes will be run. So no need to duplicate code, just extract the "duplicated code" to the parent class... – Usagi Miyamoto Aug 24 '17 at 13:22
  • I know I can use duplication and the parent class already has all the code the children need to have in common. My problem rather affects the points in which the children differ. I know I can create additional fields and methods for the children seperately, but the problem is that I need to change variables which are used in the onCreate() method of the parent (e.g. a string used in the toolbar set up there). And I don't want to put the onCreate() method into the children because that's most of the code and apart from few things it's still very similar. This is why I tried to use static fields – H.G.M. Aug 24 '17 at 19:02
  • Extract the few things into the children: Create an `onCreate` method in the children, that calls `super.onCreate` and sets the different values (EG title) as needed... – Usagi Miyamoto Aug 25 '17 at 07:06
  • Alright. But how exactly do I do this? I assume you are suggesting something like this: `public class FootballActivity extends SportActivity { @Override onCreate(savedInstanceState){ ... super.onCreate(savedInstanceState) } }` But that would just provide me the option to add additional code (indicated with "...") while still using the same variables in `super.onCreate` since I can't pass any customized arguments to `super.onCreate` – H.G.M. Aug 25 '17 at 08:02
  • Or would you recommend changing `onSaveInstanceState` in the former activity to add the necessary pieces of information to the savedInstanceState argument of `onCreate`? – H.G.M. Aug 25 '17 at 08:06
  • Or you could just set title before the `super.onCreate()` call... – Usagi Miyamoto Aug 25 '17 at 08:14
  • Ok so since I can't overwrite fields in the subclass without making them static, I need to implement a setter method to achieve my goal? Instead of using `public static String titleToolbar = "Sport"` in the superclass and `static { titleToolbar = "Football"; }` in one subclass, I put `public void setTitleToolbar(){ titleToolbar = "Sport"; }` in the superclass and ` @Override public void setTitleToolbar(){ titleToolbar = "Football"; }` in the subclass. Only thing left to do then is invoking `setTitleToolbar` in `onCreate` of the superclass, correct? – H.G.M. Aug 25 '17 at 12:22
  • Added example code to the answer... – Usagi Miyamoto Aug 25 '17 at 12:31
  • Thank you very much. This is helping a lot. I got it to work now and the bug has diappeared, too. So seriously: big, big thanks ! – H.G.M. Aug 25 '17 at 13:15
  • I was trying something similar earlier, because actually this approach seems more obvious than what I had postet originally. But I was not putting `titleToolbar="football";` in `onCreate` of the subclass, but outside of it. This gave an error in Android Studio ("unknown class "titleToolbar""). Could you explain why putting it inside of `onCreate` avoids this error or why it even occurs when putting it outside? Everything is working now, but I suppose that would help improve my understanding which led me into this miserable situation. – H.G.M. Aug 25 '17 at 13:16
  • What do you mean "outside of it"? – Usagi Miyamoto Aug 25 '17 at 13:19
  • Referring to your edit on your answer I mean putting the line `titleToolbar = "Football";` in front of the line `@Overriden` - thus extracting it out of onCreate while still leaving it in the subclass. – H.G.M. Aug 25 '17 at 13:23
  • You cannot put statements in a class but outside methods or initializers... – Usagi Miyamoto Aug 25 '17 at 13:25
  • Oh ok, well that's important to know. Thanks again. – H.G.M. Aug 25 '17 at 13:43