My presentation is working finally. I have one main activity for my first screen and one Presentation for my second Screen.
My problem is, that I can't change the content on my presentation view.
Why can't I change a TextView after the presentation is shown on the second screen?
Calling the method changeText("Test123") in the MainActivity crashes my app.
public class MainActivity extends Activity {
private PresentationActivity presentationActivity;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// init Presentation Class
DisplayManager displayManager = (DisplayManager) this.getSystemService(Context.DISPLAY_SERVICE);
Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
if (presentationDisplays.length > 0) {
// If there is more than one suitable presentation display, then we could consider
// giving the user a choice. For this example, we simply choose the first display
// which is the one the system recommends as the preferred presentation display.
Display display = presentationDisplays[0];
PresentationActivity presentation = new PresentationActivity(this, display);
presentation.show();
this.presentationActivity = presentation;
}
}
public void changeText (String s) {
this.presentationActivity.setText(s);
}
}
public class PresentationActivity extends Presentation {
private TextView text;
private PresentationActivity presentation;
public PresentationActivity(Context outerContext, Display display) {
super(outerContext, display);
// TODO Auto-generated constructor stub
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_presentation);
TextView text = (TextView) findViewById(R.id.textView1);
this.text = text;
// works fine:
text.setText("test");
}
public void setText(String s){
// error
this.text.setText(s);
}
Well, I looked in the LogCat.
The exception was:
E/AndroidRuntime(13950): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
The code in my MainActivity runs on another thread. To do UI work from here I need to use runOnUiThread. This solution I found in this answer.
My changeText methode looks like this now:
public void changeText (String s) {
runOnUiThread(new Runnable() {
public void run() {
presentationActivity.setImageView(position);
}
});
}
Thanks for the help! Now I know how to use LogCat for things like that.
You got this issue because the context of presentation is different from that of the containing Activity:
A Presentation is associated with the target Display at creation time
and configures its context and resource configuration according to the
display's metrics.
Notably, the Context of a presentation is different from the context
of its containing Activity. It is important to inflate the layout of a
presentation and load other resources using the presentation's own
context to ensure that assets of the correct size and density for the
target display are loaded.
Hope this will justify your mentioned solution as well.
Related
I have a Parent activity that sets a view on Resume based on some check like this :
public class AppLockActivity extends AppCompatActivity {
#BindView(R.id.btnSubmit)
Button submitButton;
private static final String TAG = "AppLockActivity";
private static TimeElapsed timeElapsedInstance;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
timeElapsedInstance = TimeElapsed.getInstance();
timeElapsedInstance.resetTime();
timeElapsedInstance.setStartTime();
}
#Override
protected void onResume() {
super.onResume();
//check if app has passed a time threshold
if(timeElapsedInstance.getStartTime() != 0){
timeElapsedInstance.setEndTime(Calendar.getInstance().getTimeInMillis());
long threshold = timeElapsedInstance.getEndTime()-timeElapsedInstance.getStartTime();
Log.d(TAG,"Threshold : "+threshold);
//Current timeout threshold set to 30s
if(threshold>30000){
setContentView(R.layout.activity_app_lock);
ButterKnife.bind(this);
}else{
}
}
}
#OnClick(R.id.btnSubmit) void onSubmit() {
//destroy current(Parent) view and show the previous
}
}
This activity is extended by other activities like MainAcitivty ,etc...
public class MainActivity extends AppLockActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
}
}
When the app goes in background and is resumed the onResume function is called and based on the check the new View is set - R.layout.activity_app_lock. What I want to do is onClick of the submit button in this view I want to destroy the current view i.e (R.layout.activity_app_lock) and show the previous view that was in the child activity like MainActivity (R.layout.activiyt_main)...
Anybody have any idea how can I do this?
Thanks
You can actually call setContentView again with a different view. All your bindings need to be reset and your On_____Listeners need to be cleared or else you'll get a memory leak. Other than that, it'll be up and ready for you to go.
Though I suggest an alternative approach to changing the layout. Instead, create a new Activity that you start in replacement of the layout your currently submitting. Then, rather than worrying about leaks, you just call finish() on the lock Activity when the user submits. The effect would be the same and it would be more versatile (In my opinion).
I have a lot of Widgets in my MainActivity, and they all have to be initalized. But when I initialize them all in the OnCreate method, the OnCreate method doesn't look organized anymore. So should I initialize them in another method in my MainActivity?
Absolutely...
Your code must be readable, not everyone think of it..!! :(
Take an example as below...
ublic class ActHome extends AppCompatActivity implements View.OnClickListener {
RelativeLayout layoutToBeHidden;
TextView tvName;
Button btnOk;
#Override
protected void onCreate(Bundle savedInstanceState) {
// do the preprocessing here
setContentView(R.layout.activity_home);
initialize();
populate();
}
private void initialize() {
// bind all your view from xml here
layoutToBeHidden = (RelativeLayout) findViewById(R.id.LayoutToBeHiddenActHome);
tvName = (RelativeLayout) findViewById(R.id.tvNameActHome);
btnOk = (RelativeLayout) findViewById(R.id.btnOkActHome);
//then set all the listeners etc.
btnOk.setOnClickListener(this);
}
private void populate() {
// populate the data here e.g. from database etc.
// and bind this data to the view etc.
String name = "android";
tvName.setText(name);
}
#Override
public void onClick(View v) {
Toast.makeText(context, "OK clicked", Toast.LENGTH_SHORT).show();
}
Yes, You can use a framework like Butter Knife to reduce code. http://jakewharton.github.io/butterknife/
You can use RoboGuice library to make your onCreate() more readable.
RoboGuice 3 slims down your application code. Less code means fewer opportunities for bugs. It also makes your code easier to follow -- no longer is your code littered with the mechanics of the Android platform, but now it can focus on the actual logic unique to your application.
To give you an idea, take a look at this simple example of a typical Android activity:
class AndroidWay extends Activity {
TextView name;
ImageView thumbnail;
LocationManager loc;
Drawable icon;
String myName;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
name = (TextView) findViewById(R.id.name);
thumbnail = (ImageView) findViewById(R.id.thumbnail);
loc = (LocationManager) getSystemService(Activity.LOCATION_SERVICE);
icon = getResources().getDrawable(R.drawable.icon);
myName = getString(R.string.app_name);
name.setText( "Hello, " + myName );
}
}
This example is 19 lines of code. If you're trying to read through onCreate(), you have to skip over 5 lines of boilerplate initialization to find the only one that really matters: name.setText(). And complex activities can end up with a lot more of this sort of initialization code.
Compare this to the same app, written using RoboGuice:
#ContentView(R.layout.main)
class RoboWay extends RoboActivity {
#InjectView(R.id.name) TextView name;
#InjectView(R.id.thumbnail) ImageView thumbnail;
#InjectResource(R.drawable.icon) Drawable icon;
#InjectResource(R.string.app_name) String myName;
#Inject LocationManager loc;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
name.setText( "Hello, " + myName );
}
}
In this example, onCreate() is much easier to take in at a glance.
RoboGuice's goal is to make your code be about your app, rather than be about all the initialization and lifecycle code you typically have to maintain in Android.
I hope it helps!
I stuck at this issue many times and I passed the problem in different ways and I'm not sure that I made it in the right way.
I simplified the problem in a the following example. I know that I can pass only the data to the class but I do want to pass the editText cause I have this problem with more difficult UI controls.
mainactivity.java
public class mainactivity extends Activity {
public EditText clickEditText;
int count =0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
newTxt();
}
public void newTxt() {
txt = new MyText(context);
txt.updateTextEdit("Main Activity");
}
}
myText.java
public class MyText
{
private Context _context;
// constructor
public MyText(Context context)
{
_context = context;
}
public void updateTextEdit(String str)
{
private EditText strEditText;
strEditText= (EditText)findViewById(_context.R.id.editTextClick); // ????
strEditText.setText(str + " and myTxt");
}
}
if you could explain me how to fix the updateTextEdit function. i passed the context of the main activity. How can I change the editText? Thank you very much!!!
If you really want to do this this way, you need to save a reference to Activity, not Context. Like this:
public class MyText
{
private Activity _activity;
// constructor
public MyText(Activity activity)
{
_activity= activity;
}
public void updateTextEdit(String str)
{
private EditText strEditText;
strEditText= (EditText)activity.findViewById(R.id.editTextClick);
strEditText.setText(str + " and myTxt");
}
}
and in newTxt() you will need to change:
txt = new MyText(context);
to:
txt = new MyText(this);
But wouldn't it be easier to just put this method inside your activity? Why do you want it in another class? If it really needs to be in another class, you could make that class an inner class of your activity and you would still have access to the activity's methods and member variables.
There's a similar question here
How to access Activity UI from my class?
You didn't say how you obtained the context, you should use this and get the mainactivity in the other class. not context.
then you can call runOnUIThread to perform UI updates.
I'm trying to program a syntax highlighter for Android. The highlighting algorithm which runs in a separate AsyncTask thread itself works great, and returns a SpannableString with all the necessary formatting.
However, whenever I do editText.setText(mySpannableString, BufferType.SPANNABLE) to display the highlighted text the EditText scrolls back to the beginning and selects the beginning of the text.
Obviously, this means that the user cannot keep typing whilst the text is being processed by the syntax highlighter. How can I stop this? Is there any way I can update the text without the EditText scrolling? Here is an outline of the code:
public class SyntaxHighlighter extends Activity {
private EditText textSource;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.editor);
textSource = (EditText) findViewById(R.id.codeSource);
// Syntax Highlighter loaded text
new XMLHighlighter().execute(textSource.getText().toString());
}
// Runs on Asyncronous Task
private class XMLHighlighter extends AsyncTask<String, Void, SpannableString> {
protected SpannableString doInBackground(String... params) {
return XMLProcessor.HighlightXML(params[0]);
}
protected void onPostExecute(SpannableString HighlightedString) {
textSource.setText(HighlightedString, BufferType.SPANNABLE);
}
}
}
Alternatively, there is a method called setTextKeepState(CharSequence text). See TextView docs.
I suggest the following:
protected void onPostExecute(SpannableString HighlightedString) {
int i = textSource.getSelectionStart();
textSource.setText(HighlightedString, BufferType.SPANNABLE);
textSource.setSelection(i);
}
to place the cursor back to its position after you changed the content.
I have a View that was created on runtime then I draw some canvas on that View(runtime) after that I rotated my screen.All data was gone(reset).So I put the some code in AndroidManifest.xml like this
android:configChanges="keyboardHidden|orientation"
in my <activity> then I put a #Override function
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setContentView(R.layout.main);
LinearLayout layout = (LinearLayout) findViewById(R.id.myPaint);
layout.addView(mView);
}
but everything couldn't solved my problem.I want to keep my data from View(runtime) on every single rotation.
That's my onCreate function.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mView = new MyView(this);
setContentView(mView);
mView.requestFocus();
setContentView(R.layout.main);
LinearLayout layout = (LinearLayout) findViewById(R.id.myPaint);
layout.addView(mView);
}
You need to save and load the data you want to retain. Even though you're handling the screen rotation yourself when you modified the Manifest the way you did, you're still reloading the view yourself. Reread the reference document on Handling Runtime Changes. You need to store your data and reload it accordingly. Otherwise it will be lost when the application restarts or when you reload your ContentView.
http://developer.android.com/guide/topics/resources/runtime-changes.html
You could approach this a few ways.
I assume MyView is your own class which extends View. If so there are two methods which you may care to know, onSaveInstanceState() and onRestoreInstanceState(). When saving you create a parcelable that will contain enough data for you to re-render your view if it were to be destroyed and recreated.
class MyView extends View {
private String mString;
onDraw(Canvas v) { ... }
Parcelable onSaveInstanceState() {
Bundle b = new Bundle();
bundle.putString("STRING", mString);
return b;
void onRestoreInstanceState(Parcelable c) {
Bundle b = (Bundle) c;
mString = bundle.getString("STRING", null);
}
}
Activity has similar state saving mechanics allowed in onCreate and onSaveInstanceState() (inside Activity, not View in this case) which will allow the activity to reset the state of it's view to the state it desires.
This should solve most of your worries. If you are wanting to use the onConfigurationChanged method, then you should reclarify your question as it is not clear what the current behavior is that you aren't expecting in each situation (only using onConfigurationChanged, or only using onCreate, or using both, etc).
I've just used my data-class as singleton (java-pattern).
And it works fine.
--> Application is a Stop-Timer for Racing, where i can stop time from different opponents on the track, so i need the data for longer time, also if the view is repainted.
regz
public class Drivers {
// this is my singleton data-class for timing
private static Drivers instance = null;
public static Drivers getInstance() {
if (instance == null) {
instance = new Drivers();
}
return instance;
}
// some Timer-Definitions.......
}
Then in MainActivity:
// now the class is static, and will alive during application is running
private Drivers drivers = Drivers.getInstance();
#Override
public void onClick(View v) {
if (v == runButton1) {
drivers.startTimer1();
// do some other crazy stuff ...
}
}
// here i put out the current timing every second
private myUpdateFunction(){
time01.setText(drivers.getTimer1());
// update other timers etc ...
}