I'm writing a game to help teach my son some phonics: it's my first attempt at programming in Java, although I've previously used other languages. The game has four activities: a splash screen which initializes an array of variables before you dismiss it; another to choose a user; a third to choose which level of the game to play; and a fourth to actually play the game.
My problem was that if you go in and out of the game activity repeatedly, that activity would eventually crash -- logcat showed an OOM error. Watching the heap size as I did this, and looking at a heap dump with MAT, it looked as though I was leaking the whole of the fourth activity -- GC was just not being triggered.
I've tried lots of things to track down and fix the leak -- most of which are, I'm sure improvements (e.g. getting rid of all non-static inner classes from that activity) without fixing the problem. However, I've just tried running the same thing on an emulator (same target and API as my device) and there's no leak -- heap size goes up and down, GC is regularly triggered, it doesn't crash.
So I was going to post the code for the activity on here and ask for help spotting what might be causing the leak, but I'm no longer sure that's the right question. Instead I'm wondering why it works on the emulator, but not the phone... Does anyone have any ideas?
IDE: Android Studio 2.1
Target: Android 6, API 23 (Minimum SDK 8)
Emulator: Android Studio
Device: Sony Xperia Z2 (Now running 6.0.1, but I had the same issue pre recent update, i.e. on API 22)
Code for the activity:
public class GameActivity extends AppCompatActivity implements TextToSpeech.OnInitListener {
//TTS Object
private static TextToSpeech myTTS;
//TTS status check code
private int MY_DATA_CHECK_CODE = 0;
//LevelChooser request code
public static Context gameContext;
private int level;
public static String user;
private Typeface chinacat;
public static Activity gameActivity = null;
private static int[] goldstars = {R.drawable.goldstar1, R.drawable.goldstar2, R.drawable.goldstar3};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
gameActivity = this;
gameContext = this;
level = getIntent().getIntExtra("level", 1);
user = getIntent().getStringExtra("user");
chinacat = Typeface.createFromAsset(getAssets(), "fonts/chinrg__.ttf");
Intent checkTTSIntent = new Intent();
checkTTSIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkTTSIntent, MY_DATA_CHECK_CODE);
}
#Override
public void onStop() {
if (myTTS != null) {
myTTS.stop();
}
super.onStop();
}
#Override
public void onDestroy() {
if (myTTS != null) {
myTTS.shutdown();
}
Button ok_button = (Button) findViewById(R.id.button);
ok_button.setOnClickListener(null);
ImageView tickImageView = (ImageView) findViewById(R.id.tickImageView);
tickImageView.setOnClickListener(null);
super.onDestroy();
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_DATA_CHECK_CODE) {
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
myTTS = new TextToSpeech(this, this);
} else {
Intent installTTSIntent = new Intent();
installTTSIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installTTSIntent);
}
}
}
public void onInit(int initStatus) {
//if tts initialized, load layout and level and assign listeners for layout elements
if (initStatus == TextToSpeech.SUCCESS) {
myTTS.setLanguage(Locale.ENGLISH);
setContentView(R.layout.activity_main);
ImageView imageView = (ImageView) findViewById(R.id.myImageView);
PhonemeGroup levelGroup = MainActivity.gamelevel[level]; //set possible words
levelGroup.setSubset(); //randomize subset of possible words for actual test
PhonicsWord[] testSet = levelGroup.getSubset(); //fill array of test words
TextView[] targetView = new TextView[3]; //textviews for beginning, middle & end of word
targetView[0] = (TextView) findViewById(R.id.targetWord0);
targetView[1] = (TextView) findViewById(R.id.targetWord1);
targetView[2] = (TextView) findViewById(R.id.targetWord2);
TextView[] answersView = new TextView[3]; //textviews for possible user answer choices
answersView[0] = (TextView) findViewById(R.id.letter0);
answersView[1] = (TextView) findViewById(R.id.letter1);
answersView[2] = (TextView) findViewById(R.id.letter2);
//set first target word, image for word, and possible answers
testSet[0].setWord(levelGroup, targetView, answersView, imageView);
testSet[0].speakWord(myTTS);
//subset index is equal to array index for testSet, but visible to & settable by methods
levelGroup.setSubsetIndex(0);
for(int i=0; i<3; i++) {
answersView[i].setTypeface(chinacat);
}
TextView letter0 = (TextView) findViewById(R.id.letter0);
letter0.setOnClickListener(new LetterOnClickListener(testSet, levelGroup, targetView, answersView, 0) );
TextView letter1 = (TextView) findViewById(R.id.letter1);
letter1.setOnClickListener(new LetterOnClickListener(testSet, levelGroup, targetView, answersView, 1) );
TextView letter2 = (TextView) findViewById(R.id.letter2);
letter2.setOnClickListener(new LetterOnClickListener(testSet, levelGroup, targetView, answersView, 2) );
Button ok_button = (Button) findViewById(R.id.button);
ok_button.setOnClickListener(new OKButtonOnClickListener(testSet, levelGroup, targetView, level) );
ImageView tickImageView = (ImageView) findViewById(R.id.tickImageView);
tickImageView.setOnClickListener(new TickClick(myTTS, testSet, levelGroup, targetView, answersView, imageView) );
imageView.setOnClickListener(new WordImageClick(testSet, levelGroup) );
}
/*else if TODO*/
}
private static class WordImageClick implements View.OnClickListener {
//speaks the test word when the test image is clicked
PhonicsWord[] testSet;
PhonemeGroup levelGroup;
public WordImageClick(PhonicsWord[] testSet, PhonemeGroup levelGroup) {
this.testSet = testSet;
this.levelGroup = levelGroup;
}
#Override
public void onClick(View view) {
testSet[levelGroup.getSubsetIndex()].speakWord(myTTS);
}
}
private static class LetterOnClickListener implements View.OnClickListener {
PhonemeGroup levelGroup;
PhonicsWord currentWord;
PhonicsWord[] testSet;
TextView[] targetView;
TextView[] answersView;
int item;
int phonemeclicked;
public LetterOnClickListener(PhonicsWord[] testSet, PhonemeGroup levelGroup, TextView[] targetView, TextView[] answersView, int phonemeclicked) {
this.testSet = testSet;
this.levelGroup = levelGroup;
this.targetView = targetView;
this.answersView = answersView;
this.phonemeclicked = phonemeclicked;
}
#Override
public void onClick(View view) {
this.item = this.levelGroup.getSubsetIndex();
this.currentWord = this.testSet[item];
int i = currentWord.getOmit_index();
targetView[i].setText(answersView[phonemeclicked].getText());
}
}
private void crossClick(View view) {
view.setVisibility(View.INVISIBLE);
if(view.getTag()==4){
finish();
}
}
The static variable gameActivity is used so that when you've finished a level an external class can call GameActivity.gameActivity.finish() after it's displayed how many stars you've got for the level (it's also used to call GameActivity.gameActivity.findViewById in another external class).
public class ShowStarsWithDelay extends Handler {
public void handleMessage(Message msg) {
ImageView starView = (ImageView) ((LevelEndScreens) msg.obj).starView;
ImageView highscoreView = (ImageView) ((LevelEndScreens) msg.obj).highscoreView;
int num_currentstars = (int) ((LevelEndScreens) msg.obj).num_currentstars;
int num_finalstars = (int) ((LevelEndScreens) msg.obj).num_finalstars;
Boolean highscore = (Boolean) ((LevelEndScreens) msg.obj).highscore;
int[] goldstars = (int[])((LevelEndScreens) msg.obj).goldstars;
if(num_currentstars == num_finalstars) {
if(!highscore) {
starView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
GameActivity.gameActivity.finish();
}
});
}
else {
highscoreView.setImageResource(R.drawable.highscore);
highscoreView.setVisibility(View.VISIBLE);
highscoreView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
GameActivity.gameActivity.finish();
}
});
}
}
else {
starView.setImageResource(goldstars[num_currentstars++]);
Message message = new Message();
LevelEndScreens endScreens = new LevelEndScreens(starView, highscoreView, num_currentstars, num_finalstars, highscore, goldstars);
message.obj = endScreens;
this.sendMessageDelayed(message, 1000);
}
}
}
In general, you want to avoid having any static reference to a Context anywhere in your application (this includes Activity classes, of course). The only reference to a Context which MAY be acceptable is referencing the application context (as there is only one and it is always in memory while your app is alive anyway).
If you need a reference to the calling activity in one of your children, you'll need to pass the context as a parameter, or else use one of the child views methods to retrieve the context (such as getContext() for views and fragments).
More information that should help understand memory leaks and why this is important is here:
http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
As an example, in your code for calling finish(), you could safely change it to this:
highscoreView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (v.getContext() instanceof Activity) {
((Activity)v.getContext()).finish();
}
}
});
To sum up, in order to fix your memory leaks, you'll need to remove the static keyword for all of your Context fields.
Related
I am getting an error which says:
java.lang.IllegalStateException: System services not available to Activities before onCreate()
although every action I do are after onCreate().
I have a custom adapter which has 4 buttons. When one of the button is clicked, it ultimately calls the updateList() function which is supposed to update the list with the new details.
Here's the code:
MainActivity.java
public class MainActivity extends Activity {
static int accountsCount = 0;
static FileWorker fileWorker;
static File directory;
ListView ledgerListView;
TextView noAccountsTextView;
TextView accountHierarchy;
EditText accountName;
EditText accountLimit;
AlertDialog.Builder accountDialogBuilderChild;
AlertDialog accountDialogChild;
static ArrayList<AccountsView> ledgerList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean introDone = preferences.getBoolean("intro_done", false);
boolean howToDone = preferences.getBoolean("howto_done", false);
boolean prefFilesCreated = preferences.getBoolean("files_created", false);
if(!introDone || !howToDone) {
Intent introActivity = new Intent(this, IntroActivity.class);
startActivity(introActivity);
}
noAccountsTextView = findViewById(R.id.noAccountsTextView);
ledgerListView = findViewById(R.id.mainListView);
fileWorker = new FileWorker();
directory = getFilesDir();
if(!prefFilesCreated) {
boolean filesCreated = fileWorker.createFiles(directory);
if(filesCreated) {
SharedPreferences.Editor prefEditor = preferences.edit();
prefEditor.putBoolean("files_created", true);
prefEditor.apply();
}
}
accountsCount = fileWorker.countAccounts(directory);
setMainActivityView();
}
public void addChildAccountDialog(Context context, int pos) {
String hierarchy = ledgerList.get(pos).getAccountName();
String renewalType = ledgerList.get(pos).getRenewalType();
accountDialogBuilderChild = new AlertDialog.Builder(context);
accountDialogBuilderChild.setTitle(R.string.add_child_account_dialog_title);
accountDialogBuilderChild.setPositiveButton("Ok",
(dialogInterface, i) -> addChildAccount(hierarchy, renewalType));
accountDialogBuilderChild.setNegativeButton("Cancel",
(dialogInterface, i) -> dialogInterface.cancel());
accountDialogBuilderChild.setView(R.layout.dialog_add_child_account);
accountDialogChild = accountDialogBuilderChild.create();
accountDialogChild.show();
accountHierarchy = accountDialogChild.findViewById(R.id.accountHierarchyValueTV);
accountHierarchy.setText(hierarchy);
accountName = accountDialogChild.findViewById(R.id.accountNameDialogET);
accountLimit = accountDialogChild.findViewById(R.id.accountLimitDialogET);
}
private void addChildAccount(String hierarchy, String renewalType) {
String accName = hierarchy.concat(formatAccountName(accountName
.getText().toString()));
double accLimit = Double.parseDouble(accountLimit.getText().toString());
fileWorker.addChildAccount(directory,
accName,
renewalType,
accLimit);
setMainActivityView();
}
private void setMainActivityView() {
accountsCount = fileWorker.countAccounts(directory);
if(accountsCount <= 0) {
ledgerListView.setVisibility(View.GONE);
noAccountsTextView.setVisibility(View.VISIBLE);
} else {
updateList();
ledgerListView.setVisibility(View.VISIBLE);
noAccountsTextView.setVisibility(View.GONE);
}
}
public void updateList() {
fileWorker.sortAccounts(directory);
ledgerList = fileWorker.getAccountsList(directory);
AccountsViewAdapter ledgerAdapter = new
AccountsViewAdapter(this, ledgerList);
ledgerListView.setAdapter(ledgerAdapter);
}
public String formatAccountName(String accName) {
accName = accName.trim().toLowerCase();
accName = accName.replace(' ', '_');
accName = accName.replace('/', '_');
if(accName.charAt(0) != '/') {
accName = "/".concat(accName);
}
return accName;
}
public void onBackPressed() {
new AlertDialog.Builder(this)
.setTitle("Exit")
.setMessage("Are you sure?")
.setPositiveButton("Yes", (dialog, which) -> this.finishAffinity())
.setNegativeButton("No", null)
.show();
}
}
Here's the cutomAdapter code:
public class AccountsViewAdapter extends ArrayAdapter<AccountsView> {
TextView accountName;
TextView renewalType;
TextView limitValue;
TextView balanceValue;
Button buttonAddAccount;
Button buttonEditAccount;
Button buttonIncreaseBalance;
Button buttonDecreaseBalance;
MainActivity mainActivity;
public AccountsViewAdapter(Context context, ArrayList<AccountsView> arrayList) {
super(context, 0, arrayList);
}
public View getView(int position, View convertView, ViewGroup parent) {
View currentItemView = convertView;
if(currentItemView == null) {
currentItemView = LayoutInflater.from(getContext())
.inflate(R.layout.listview_row, parent, false);
}
AccountsView currentAccount = getItem(position);
assert currentAccount != null;
accountName = currentItemView.findViewById(R.id.accountNameValueTextView);
renewalType = currentItemView.findViewById(R.id.textViewRenewalTypeValue);
limitValue = currentItemView.findViewById(R.id.limitValueTextView);
balanceValue = currentItemView.findViewById(R.id.balanceValueTextView);
buttonAddAccount = currentItemView.findViewById(R.id.addAccountButton);
buttonEditAccount = currentItemView.findViewById(R.id.editAccountButton);
buttonIncreaseBalance = currentItemView.findViewById(R.id.increaseBalanceButton);
buttonDecreaseBalance = currentItemView.findViewById(R.id.decreaseBalanceButton);
accountName.setText(currentAccount.getAccountName());
renewalType.setText(currentAccount.getRenewalType());
limitValue.setText(currentAccount.getAmountLimit());
balanceValue.setText(currentAccount.getBalanceValue());
mainActivity = new MainActivity();
buttonAddAccount.setOnClickListener(v -> {
mainActivity.addChildAccountDialog(buttonAddAccount.getContext(), position);
});
return currentItemView;
}
}
As far as I understood, the error happens when I click on the "buttonAddAccount" button. I tried replacing the context in MainActivity with MainActivity.this, but that didn't help.
Here's the error log:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: org.biotstoiq.seshat, PID: 18232
java.lang.IllegalStateException: System services not available to Activities before onCreate()
at android.app.Activity.getSystemService(Activity.java:6715)
at android.view.LayoutInflater.from(LayoutInflater.java:299)
at android.widget.ArrayAdapter.<init>(ArrayAdapter.java:216)
at android.widget.ArrayAdapter.<init>(ArrayAdapter.java:210)
at android.widget.ArrayAdapter.<init>(ArrayAdapter.java:196)
at org.biotstoiq.seshat.AccountsViewAdapter.<init>(AccountsViewAdapter.java:28)
at org.biotstoiq.seshat.MainActivity.updateList(MainActivity.java:179)
at org.biotstoiq.seshat.MainActivity.setMainActivityView(MainActivity.java:170)
at org.biotstoiq.seshat.MainActivity.addChildAccount(MainActivity.java:161)
at org.biotstoiq.seshat.MainActivity.lambda$addChildAccountDialog$2$org-biotstoiq-seshat-MainActivity(MainActivity.java:140)
at org.biotstoiq.seshat.MainActivity$$ExternalSyntheticLambda2.onClick(Unknown Source:6)
at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:201)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:230)
at android.app.ActivityThread.main(ActivityThread.java:7875)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:526)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034)
I/Process: Sending signal. PID: 18232 SIG: 9
The fileWorker code is not here since I didn't think it's required here.
Never instantiate an Activity with new ActivityClass. That doesn't properly initialize it. Only the Android framework can properly initialize an Activity, and you do that via an Intent.
Even if you could initialize an Activity like that, it would be wrong. You don't want to call that function on a new instance, you want to call it on the instance you're running in.
You shouldn't have any view or adapter class require a specific Activity anyway. You should use interfaces that pass handlers into the adapter. That method is far easier to test and easier to get right. Even if your code worked it would be next to impossible to unit test.
I am working on a Quiz app. First when a user opens the app they go to the MainActivity, from there when they press start they go to the Categories Activity , from there after selecting a category they go to the Sets Activity, from there after selecting a set the go to the Questions Activity and finally after completing all the questions they reach the Score Activity. Here in the score activity when the click on Done button they are redirected to the MainActivity. In the Score Activity i want to change the color of the Set that they completed to green instead of the default color. How can i do this? I created a sets item layout xml file and used an adapter to fill the gridview in the Sets Activity with views from the adapter. Currently i am getting a null object reference after clicking the Done button in the ScoreActivity.
Here is the code :
SetsAdapter.java
public class SetsAdapter extends BaseAdapter {
private int numOfSets;
public SetsAdapter(int numOfSets) {
this.numOfSets = numOfSets;
}
#Override
public int getCount() {
return numOfSets;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
View view;
if(convertView == null){
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.set_item_layout, parent, false);
}
else {
view = convertView;
}
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent questionIntent = new Intent(parent.getContext(), QuestionActivity.class);
questionIntent.putExtra("SETNUM", position +1);
parent.getContext().startActivity(questionIntent);
}
});
((TextView) view.findViewById(R.id.setNumber)).setText(String.valueOf(position+1));
return view;
}
}
SetsActivity.java
public class SetsActivity extends AppCompatActivity {
private GridView sets_grid;
private FirebaseFirestore firestore;
public static int categoryID;
private Dialog loadingDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sets);
Toolbar toolbar = (Toolbar)findViewById(R.id.set_toolbar);
setSupportActionBar(toolbar);
String title = getIntent().getStringExtra("CATEGORY");
categoryID = getIntent().getIntExtra("CATEGORY_ID",1);
getSupportActionBar().setTitle(title);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
sets_grid = findViewById(R.id.sets_gridView);
loadingDialog = new Dialog(SetsActivity.this);
loadingDialog.setContentView(R.layout.loading_progressbar);
loadingDialog.setCancelable(false);
loadingDialog.getWindow().setBackgroundDrawableResource(R.drawable.progress_background);
loadingDialog.getWindow().setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
loadingDialog.show();
firestore = FirebaseFirestore.getInstance();
loadSets();
}
private void loadSets() {
firestore.collection("Quiz").document("CAT" + String.valueOf(categoryID))
.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot doc = task.getResult();
if (doc.exists()) {
long sets = (long) doc.get("SETS");
SetsAdapter adapter = new SetsAdapter(Integer.valueOf((int)sets));
sets_grid.setAdapter(adapter);
} else {
Toast.makeText(SetsActivity.this, "No Sets Exists!", Toast.LENGTH_SHORT).show();
finish();
}
} else {
Toast.makeText(SetsActivity.this, task.getException().getMessage(), Toast.LENGTH_SHORT).show();
}
loadingDialog.cancel();
}
});
}
#Override
public boolean onOptionsItemSelected(#NonNull MenuItem item) {
if(item.getItemId() == android.R.id.home)
finish();
return super.onOptionsItemSelected(item);
}
}
ScoreActivity.java
public class ScoreActivity extends AppCompatActivity {
private TextView score;
private Button done;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_score);
score = findViewById(R.id.score_tv);
done = findViewById(R.id.score_activity_done);
String score_str = getIntent().getStringExtra("SCORE");
final int setNum = getIntent().getIntExtra("SetNum", 1);
score.setText(score_str);
done.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Here is the issue I am facing
View view = findViewById(R.id.setNumber);
view.setBackgroundColor(Color.GREEN);
Intent mainIntent = new Intent(ScoreActivity.this, MainActivity.class);
startActivity(mainIntent);
ScoreActivity.this.finish();
}
});
}
}
As your activity Sequence is MainActivity -> Categories -> Sets -> Scores.
You've two options to change the color with two different life cycle of the change.
To change the color on a temporary basis, this will reset itself after closing the app or resrtating the 'Sets' activity. It can be done in two ways: Using Public Static Variable and using a public function.
To change the color on a permanent basis until the app is uninstalled/reinstalled. You should use SharedPreferences. SharedPreferences acts like a private data stored in device's memory for further use and it stays there unchanged until and unless the app is removed/data is cleared. Although, apps with root permission can access any app's SharedPreferences data and can modify it as well.You can use SharedPreferences as explained here. Or, you can use some library to access it an easy way. The way I use it in all my apps is TinyDB(it's just a java/kotlin file). This works as:
//store the value from ScoreActivity after completion as
TinyDB tinyDB = TinyDB(this);
tinyDB.putBoolean("isSet1Completed",true);
//access the boolean variable in SetsActivity to change the color of any set that
//is completed and if it's true, just change the color.
TinyDB tinyDB = TinyDB(this);
Boolean bool1 = tinyDB.getBoolean("isSet1Completed");
But, it's your choice what way you want to prefer.
Now, this was about the lifecycle of the change you'll do: Temp or Permanent. Now, we'll talk about how you change the color.
Using public static variable in Sets activity. What you can do is you can set the imageView/textview whose background you want to change as public static variable. Remember, this idea is not preferred as it causes memory leak but it's just easy.
Declare it as public static ImageView imageview;(or TextView) intialize it in the
onCreated() as imageView = finViewById(R.id.viewId); in Sets activity. Call
it as new SetsActivity().imageView.setBackgroundColor(yourColor); in ScoreActivity.
Second way is to create a public function in SetsAcitvity, putting the color change code in it, and then calling it from the ScoreActivity. Just declare it as public void changeColor(){ //your work} and call it from ScoreActivity as new SetsActivity().changeCOlor(). You can also pass some arguments to the function like setId.
I've provided you every thing you need. Rest you should figure out yourself to actually learn it and not copy it.
I think simply you add flag in MainActivity.
for example, add flag in MainActivity.
boolean isFromDone = false;
and when done clicked,
done.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Here is the issue I am facing
Intent mainIntent = new Intent(ScoreActivity.this, MainActivity.class);
mainIntent.putExtra("FromDone", true);
startActivity(mainIntent);
ScoreActivity.this.finish();
}
});
and in MainActivity, add this.
#Override
protected void onResume() {
super.onResume();
isFromDone = getIntent().getBooleanExtra("FromDone", false);
if(isFromDone) {
(TextView) view.findViewById(R.id.setNumber)).setBackgroundColor(Color.GREEN);
}
}
Suppose you have a Linear Layout in Activity A and you want to change it's background color from a button click which is present in Activity B.
Step 1 Create a class and declare a static variable.
class Util { private static LinearLayout mylayout ; }
Step 2
In the activity which is holding this layout, initialize it.
Util.mylayout = findviewbyid(R.id.linear);
Step 3Change the background color on button click from Activity B
onClick{
Util.mylayout.setBackgroundColor(Color.RED);
}
Here is my situation:
I have an OnCreate code like the following:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bunz = Bunz.getInstance(); //getting instance of bunz
bunz.setBunz(50);
bunz.setMoney(0);
bunz.setIncrement(1);
Button upgradeButton = (Button) findViewById(R.id.upgradeButton);
upgradeButton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view){
Intent startIntent = new Intent(getApplicationContext(), UpgradeMenu.class);
startActivity(startIntent);
}
});
moneyCount = (TextView) findViewById(R.id.moneyCount);
bunzCount = (TextView) findViewById(R.id.bunzCount);
ImageButton bun = (ImageButton) findViewById(R.id.bun);
}
Notice how in my OnCreate code, I do 2 things; first, I initialize all the values I need:
bunz.setBunz(50);
bunz.setMoney(0);
bunz.setIncrement(1);
and then I display these values on TextViews and set up some Buttons and intents:
Button upgradeButton = (Button) findViewById(R.id.upgradeButton);
upgradeButton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view){
Intent startIntent = new Intent(getApplicationContext(), UpgradeMenu.class);
startActivity(startIntent);
}
});
moneyCount = (TextView) findViewById(R.id.moneyCount);
bunzCount = (TextView) findViewById(R.id.bunzCount);
ImageButton bun = (ImageButton) findViewById(R.id.bun);
I'm new to Android studio, and here is the problem I'm having. I want to use onResume() to update these values in the TextView (I update them in another activity) every time I go back to this activity. However, if I move all the code in onCreate into onResume, then every time I go back to this activity, the values will be set to 50,0, and 1. I understand I could use a boolean, so that onCreate() triggers the first time the app is launched, but onResume() doesn't trigger, and then onResume() triggers after that, and simply copy and paste the second half of the onCreate code into onResume(), but that seems inefficient, and isn't how Android studio is designed to work. Can I somehow initialize the values in another location?
I have a global Bunz class that looks like the following:
public class Bunz {
private int bunz;
private int money;
private int increment;
//singleton code
private static Bunz instance;
private Bunz(){
}
public static Bunz getInstance(){
if (instance == null){
instance = new Bunz();
}
return instance;
}
public int getBunz() {
return bunz;
}
public void setBunz(int num){
bunz = num;
}
public int getMoney(){
return money;
}
public void setMoney(int num){
money = num;
}
public int getIncrement(){
return increment;
}
public void setIncrement(int num){
increment = num;
}
}
so maybe I could initialize these values here somehow?
Thanks!
here's one thing you could alternatively do:
public static Bunz getInstance(){
if (instance == null){
instance = new Bunz();
instance.setBunz(50);
instance.setMoney(0);
}
return instance;
}
in your instance creation here, try setting the values you want here, instead of in onCreate of the app.
you could just be making the changes in the constructor as well.
While your code uses statics, which I believe is unnecessary. Statics are not your average goto solution, they come with a hefty price of an object not eligible for GC.
You can get the result from the second activity via onActivityResult method.
First, start second activity using startAtivityForResult() //This takes in a request code(Int), it can be whatever you set.
First activity
Intent intent = new Intent(this, SecondActivity.class);
startActivityForResult(intent , 100);
Second Activity
//Do you work in the second activity, generate new data
Intent returnIntent = new Intent();
returnIntent.putExtra("bunz", 100);
returnIntent.putExtra("money", 200);
returnIntent.putExtra("increment", 2);
setResult(Activity.RESULT_OK, returnIntent);
finish();
Capture Second Activity Result
This code is supposed to be written in your first activity.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 100) { //Remember the code we set in startActivityForResult? This is where we identify our work
if(resultCode == Activity.RESULT_OK){ //Code to check if data is passed
Int bunz =data.getIntExtra("bunz")
bunz.setBunz(bunz)
.....
}
}
}
If anybody can tell me different way to do this, I would appreciate. How to reset class variable to 0, or default value? I use class variable cause I don't know another way to do this. After my game ends I place result in class variable, cause I have two rounds of my game, and after first round ends I add the result, and class variable is good for this cause even after I restart my game method it still holds my previous result. After second round is over I add that result to previous result and then close the activity and set text result as text to a button. But when I click New game, that button still holds that text, cause class variable still holds it. How to reset that class variable when I go on New game?
Here's my game code, some of it (100 points are start amount, and it gets lower in game progress):
public class Asocijacije extends Activity implements OnClickListener{
int brojPoenaAsocijacije = 100;
public static int brojPoenaUkupno;
Then I skip here a lot of code and here's where I add points. brojPoenaAsocijacije are points earned in that round:
brojPoenaUkupno = brojPoenaUkupno + brojPoenaAsocijacije;
Here's my main activity where I set points from my class variable to a button (where I added comment):
public class Izbor extends Activity implements OnClickListener{
Asocijacije poeni = new Asocijacije();
Button toploHladno, asocijacije, cigle, spojnice, nazad, poeniTH, poeniAso, poeniCigle, poeniSpojnice;
TextView naslov;
public boolean music;
MediaPlayer buttonClicks, buttonBack;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); //full screen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.izbor);
SharedPreferences getPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
music = getPrefs.getBoolean("checkbox", true);
addListenerOnButton();
}
private void addListenerOnButton() {
buttonClicks = MediaPlayer.create(this, R.raw.click);
buttonBack = MediaPlayer.create(this, R.raw.button31);
Typeface naslovType = Typeface.createFromAsset(getAssets(), "Lobster.ttf");
Typeface dugmad = Typeface.createFromAsset(getAssets(), "Bebas.ttf");
naslov = (TextView) findViewById(R.id.tvIzborNaslov);
toploHladno = (Button) findViewById(R.id.bIzbor1);
asocijacije = (Button) findViewById(R.id.bIzbor2);
cigle = (Button) findViewById(R.id.bIzbor3);
spojnice = (Button) findViewById(R.id.bIzbor4);
nazad = (Button) findViewById(R.id.bIzborNazad);
poeniTH = (Button) findViewById(R.id.bPoeniTH);
poeniAso = (Button) findViewById(R.id.bPoeniAso);
poeniCigle = (Button) findViewById(R.id.bPoeniCigle);
poeniSpojnice = (Button) findViewById(R.id.bPoeniSpojnice);
naslov.setTypeface(naslovType);
toploHladno.setTypeface(dugmad);
asocijacije.setTypeface(dugmad);
cigle.setTypeface(dugmad);
spojnice.setTypeface(dugmad);
nazad.setTypeface(dugmad);
poeniAso.setTypeface(dugmad);
toploHladno.setOnClickListener(this);
asocijacije.setOnClickListener(this);
cigle.setOnClickListener(this);
spojnice.setOnClickListener(this);
nazad.setOnClickListener(this);
}
#Override
protected void onStart() {
super.onStart();
poeniAso.setText("" + poeni.brojPoenaUkupno); //I do it here
}
public void onClick(View v) {
switch(v.getId()){
case R.id.bIzbor1:
if(music == true){
buttonClicks.start();
}
startActivity(new Intent("rs.androidaplikacije.toplo_hladno.GAME"));
break;
case R.id.bIzbor2:
if(music == true){
buttonClicks.start();
}
startActivity(new Intent("rs.androidaplikacije.toplo_hladno.ASOCIJACIJE"));
break;
case R.id.bIzbor3:
if(music == true){
buttonClicks.start();
}
break;
case R.id.bIzbor4:
if(music == true){
buttonClicks.start();
}
break;
case R.id.bIzborNazad:
if(music == true){
buttonBack.start();
}
poeniAso.setText("");
finish();
break;
}
}
}
Because I don't find the piece of code where you start the new game, I can only say:
Asocijacije.brojPoenaUkupno = 0;
Probably want to reset your state when you close the in-game activity.
#Override
protected void onDestroy() {
super.onDestroy();
Asocijacije.brojPoenaUkupno = 0;
//whatever other things need to be reset.
}
To all you that have helped me with my other questions thank you. I almost have it, but 2 final problems are preventing it from working the way i want.
These 2 classes are supposed to do as follows. 1st class gets the names of the people that want to play the game. Uses the same EditText and when they input their name they click submit. When all the names are submitted they click the done/play button which sends them and their data (how many players and names) to the next class. On class 1 i believe the error lies in the submit button. I'm trying to add all the names to an array list and I dont believe it is doing it correctly. When I run the app it takes in the names just fine from the users standpoint. But on the following screen it should display their name: (it says null so it is not getting the names correctly) and a task to do (which it does correctly).
The last thing it needs to do is on class 2 it needs to allow those buttons (failed, champ, and not bad) to only need to be clicked once (then it sets a score to the name of the person who's turn it was) and then it needs to start the next person and task. (It does neither atm). I would really appreciate help getting this blasted thing to work. Thanks to all who take the time to reply. And sorry if ur sick of seeing my help requests.
Class 1
public class Class1 extends Activity
{
int players=0, i=0;
String names[];
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.class1);
final EditText input = (EditText) findViewById(R.id.nameinput);
final ArrayList<String> names = new ArrayList<String>();
//names = new String[players];
Button submitButton = (Button) findViewById(R.id.submit_btn);
submitButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View submit1)
{
//for( i=i; i < players; i++)
//{
players++;
names.add(input.getText().toString());
//names[i] = input.getText().toString();
input.setText("");
//}
}
});
Button doneButton = (Button) findViewById(R.id.done_btn);
doneButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View done1)
{
Intent done = new Intent(Class1.this, Game.class);
Bundle bundle = new Bundle();
bundle.putStringArrayList("arrayKey", names);
done.putExtra("players", players);
//done.putExtra("names", names[players]);
startActivity(done);
}
});
}
Game Class
public class Game extends Activity
{
int players, counter=0, score, ptasks,rindex;
String[] names;
String[] tasks;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
Bundle bundle = this.getIntent().getExtras();
String[] names = bundle.getStringArray("arrayKey");
Intent game = getIntent();
players = game.getIntExtra("players", 1);
//names = game.getStringArrayExtra("names");
Random generator = new Random();
tasks = new String[10];
tasks[0]= "";
tasks[1]= "";
tasks[2]= "";
tasks[3]= "";
tasks[4]= "";
tasks[5]= "";
tasks[6]= "";
tasks[7]= "";
tasks[8]= "";
tasks[9]= "";
names = new String[players];
while (counter <5)
{
for (int i = 0; i < players; i++)
{
TextView name1 = (TextView) findViewById(R.id.pname);
name1.setText( names[i]+":");
ptasks = 10;
rindex = generator.nextInt(ptasks);
TextView task = (TextView) findViewById(R.id.task);
task.setText( tasks[rindex]);
Button failButton = (Button) findViewById(R.id.fail_btn);
failButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View failed)
{
return;
}
});
Button notButton = (Button) findViewById(R.id.notbad_btn);
notButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View notbad)
{
return;
}
});
Button champButton = (Button) findViewById(R.id.champ_btn);
champButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View champp)
{
return;
}
});
}
counter++;
}
}
}
As a side note. The things that you see within those sections that have // comments next to them I have there because i was testing out between those and the ones that arent commented out and neither worked. If you have any input on fixing any of this i appreciate it.
I see two problems with your code that might explain why you get a null for your players list in your second Activity:
In Game, String[] names = bundle.getStringArray("arrayKey"); should be
ArrayList<String> names = bundle.getStringArrayList("arrayKey");`
In Class1, you're putting the ArrayList into the Bundle(bundle.putStringArrayList("arrayKey", names);) which is pointless since bundle goes no where. You should be putting it into the Intent instead:
done.putStringListExtra("arrayKey", names);
Note that your code is all the more confusing because you have both a String [] named names and an ArrayList named names in different scopes. Decide on one (I'd recommend the List) and get rid of the other.
Also, in Game, this is unncessary:
Bundle bundle = this.getIntent().getExtras();
String[] names = bundle.getStringArray("arrayKey");
Intent game = getIntent();
players = game.getIntExtra("players", 1);
You already have the bundle just before this, so you could as well do:
Bundle bundle = this.getIntent().getExtras();
String[] names = bundle.getStringArray("arrayKey");
players = bundle.getInt("players", 1);
The basic concept is that from the calling activity, you put information into an Intent using the various putExtra() and putExtraXXX() methods. In the called activity, you get the information you had put into the Intent by either
getting a Bundle *from * the Intent via getExtras() and then getting everything put in using the various get() methods on the Bundle (not the Intent).
directly invoking the getExtraXXX() methods on the Intent.
For the second part, as your code currently stands, it simply going to loop over all the players immediately (5 times in all, I don't understand the purpose of counter).
What you should instead be doing is performing all of your processing (calculating the score for the current player, incrementing the value of the player index, setting the next task etc) only when one of the 3 buttons is pressed. If it's going to be a long-lived task, you could disable the buttons until finished in order to enforce the requirement of allowing only one button to be pressed per player. Re-enable the buttons when the next player is ready.
I don't have the energy to churn out everything you need but at a starting point, turn this:
public void onCreate(Bundle savedInstanceState)
{
//...other code here
while (counter <5)
{
for (int i = 0; i < players; i++)
{
TextView name1 = (TextView) findViewById(R.id.pname);
name1.setText( names[i]+":");
ptasks = 10;
rindex = generator.nextInt(ptasks);
TextView task = (TextView) findViewById(R.id.task);
task.setText( tasks[rindex]);
Button failButton = (Button) findViewById(R.id.fail_btn);
failButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View failed)
{
return;
}
});
Button notButton = (Button) findViewById(R.id.notbad_btn);
notButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View notbad)
{
return;
}
});
Button champButton = (Button) findViewById(R.id.champ_btn);
champButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View champp)
{
return;
}
});
}
counter++;
}
//...other code here
}
into
public void onCreate(Bundle savedInstanceState)
{
//...other code here
int i = 0;
TextView name1 = (TextView) findViewById(R.id.pname);
TextView task = (TextView) findViewById(R.id.task);
Button failButton = (Button) findViewById(R.id.fail_btn);
failButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View failed)
{
//do what must be done for the current player, calculate score, etc
prepareNextPlayer(++i, names, name1, task);
}
});
Button notButton = (Button) findViewById(R.id.notbad_btn);
notButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View notbad)
{
//do what must be done for the current player, calculate score, etc
prepareNextPlayer(++i, names, name1, task);
}
});
Button champButton = (Button) findViewById(R.id.champ_btn);
champButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View champp)
{
//do what must be done for the current player, calculate score, etc
prepareNextPlayer(++i, names, name1, task);
}
});
//...other code here
}
private void prepareNextPlayer(int i, ArrayList<String> names, String [] tasks, TextView nameField, TextView taskField)
{
if(i >= names.size())
{
//all players have been processed, what happens now?
return;
}
int rindex = generator.nextInt(10);
nameField.setText( names.get(i)+":");
task.setText( tasks[rindex]);
}