So full admission, I am a bit self taught when it comes to Android Dev, so I maybe going about this all wrong. As such I am open to suggestions! I'm essentially trying to semi-automate a task I do every day currently.
Question: How to Pass a LinearLayout, it's contents intact, between Activities?
So I have this 2nd Activity, called reportGeneratorActivity In this activity there is a Linear Layout directly under the Report Preview.
The Linear Layout itself is defined in a separate XML file as previewplate.xml
Now this Activity functions, when you put text into the upper fields it updates the preview below. Which brings me to the brick wall I'm hitting. The Goal is to take that preview plate and add it to my main activity that I've named rootActivity in the white area which is a Linear Layout itself named rootWorkingLayout.
Now the Strings from the text are all stored temporarily in reportGeneratorActivity at which point I am doing this when the button is pressed:
public void beginReport (View view) {
//Bundle the Preview
Bundle previewBundle = new Bundle();//Create the Bundle
previewBundle.putString("date", dateHolder);
previewBundle.putString("client", clientNameHolder);
previewBundle.putString("machine", machineTypeHolder);
previewBundle.putString("serial#", serialNumberHolder);
previewBundle.putString("notes", notesHolder);
// Prepare The Intent
Intent previewPasser = new Intent(this, rootActivity.class);
previewPasser.putExtras(previewBundle); // Add the Bundle to Intent
//Send Preview to Root
startActivity(previewPasser);
//Send Preview to History
//Send User to Decision Tree
}
From what I understand, I've put all the strings in Bundle previewBundle, then attached the bundle to the previewPasser intent and sent the intent back to rootActivity.
In rootActivity, within the onCreate function I have placed this code:
Bundle previewReceiver = getIntent().getExtras();
//If There is a Bundle, Process it
if(previewReceiver != null) {
newPreview(previewReceiver);
}
The Goal here is to grab the Intent, and grab the bundle then pass it to my newPreview function (currently empty) that will duplicate finished preview from report_generator_activity and desplaying within the Linear Layout: rootWorkinglayout in an identical fashion.
It's this final step that I am hitting a brick wall on, I can only assume there is an easier way, perhaps a way to duplicate the Layout and it's contents and send it over? Or if I am doing this functionally, How do I unpack the data in an identical manner?
Please forgive the verbosity and lack of images as I am a new member of the community.
Edit #1:
In response to SoroushA's excellent answer that has put me on the correct path, I've adjusted my newPreview Method to be this:
public void newPreview (Bundle previewReceiver) {
//Extract Strings from Bundle
String date = previewReceiver.getString("date");
String client = previewReceiver.getString("client");
String machine =previewReceiver.getString("machine");
String serialNum = previewReceiver.getString("serial#");
//Create New Inflater
LayoutInflater previewInflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View previewLayout = previewInflater.inflate(R.layout.previewplate, null);
//Add previewLayout to rootWorkingLayout
rootWorkingLayout.addView(previewLayout);
}
Currently, I am just trying to get the grey box of the preview plate layout to appear as it's background is defined in it's own XML file. However, nothing is occurring when I go through the process clearly due to my own error. I am unsure of what step I am missing.
Thanks in advance!
I hope that I understood your question clearly.
In your newPreview method, start by getting the Strings back from the Bundle:
public void newPreview(Bundle preview){
String dateHolder = preview.getString("date");
//similarly for other strings
}
Then inflate the LinearLayout and start setting its elements.
LayoutInflater inflater = (LayoutInflater)context.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.your_xml, null);
//call findviewbyid on the layout and set its children using the strings you extracted
How to Pass a LinearLayout, it's contents intact, between Activities?
You don't. Widgets and containers (i.e., subclasses of View) are owned by their activity.
I can only assume there is an easier way
Have only one activity, altering its UI as needed (e.g., use fragments and replace them as needed). These seem to be way too closely coupled to be two separate activities.
The Goal here is to grab the Intent, and grab the bundle then pass it to my newPreview function (currently empty) that will duplicate finished preview from report_generator_activity and desplaying within the Linear Layout: rootWorkinglayout in an identical fashion.
There is nothing intrinsically wrong with this approach. You act as though you are having problems implementing it ("How do I unpack the data in an identical manner"), but we do not have nearly enough information on which to provide you with much advice. In general, a Bundle has getter methods, to retrieve that values that you put into the Bundle via the setter methods.
Related
I am very new to Java. I am doing a school project at the moment and I have my main activity, then I have a settings activity. I am trying to modify the xml from the main activity with the settings activity. I am able to modify the settings xml file with the settings.java, but I would like to modify the main activity xml with settings.java
public class Settings extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
// Get the Intent that started this activity and extract the string
Switch switchButton;
final RelativeLayout mRelativeLayout = (RelativeLayout) findViewById(R.id.activity_settings);
final RelativeLayout mRelativeLayoutMain = (RelativeLayout) findViewById(R.id.activity_main);
switchButton = (Switch) findViewById(R.id.switch1);
switchButton.setChecked(true);
switchButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean bChecked) {
if (bChecked) {
mRelativeLayoutMain.setBackgroundColor(Color.GRAY);
mRelativeLayout.setBackgroundColor(Color.GRAY);
} else {
mRelativeLayoutMain.setBackgroundColor(Color.WHITE);
mRelativeLayout.setBackgroundColor(Color.WHITE);
}
}
});
if (switchButton.isChecked()) {
mRelativeLayoutMain.setBackgroundColor(Color.GRAY);
mRelativeLayout.setBackgroundColor(Color.GRAY);
} else {
mRelativeLayoutMain.setBackgroundColor(Color.WHITE);
mRelativeLayout.setBackgroundColor(Color.WHITE);
}}
public void toast1(View view) {
android.widget.Toast.makeText(this, "Created by Cody Walls and Tommy Serfas", android.widget.Toast.LENGTH_LONG).show();
}
/*public void switch1(View view) {
ScrollView mScrollView = (ScrollView) findViewById(R.id.scrollView);
mScrollView.setBackgroundColor(Color.GRAY);
}*/
}
In the Code I am trying to change the background of the main activity xml with :
mRelativeLayoutMain.setBackgroundColor(Color.GRAY);
and when I run the app and click the intent it will crash with the error:
"java.lang.NullPointerException: Attempt to invoke virtual method
'void android.widget.RelativeLayout.setBackgroundColor(int)' on a null
object reference"
I think the easiest way is to create an PreferenceManager.SharedPreferences, in which I recommend you to store current app data. This will help you not to loose any changes in app after you exit the it. Here is short instructions:
Create button in settings activity which will change something in main activity.
Create onClickListener for your button.
Use .SharedPreferences to store was you button clicked or not. (I recommend storing boolean variables, this way you can store was button clicked or not.)
I both of your activities in onCreate method call .getSharedPreferences to read saved app values. (I mean to read was the button clicked or not.)
Use app values you got from 4. to change any element in activity. (For example if you stored that button was clicked, then change some TextView text or etc.)
I hope you understood the idea.
Link to the Android developer tutorial about App key values storing & saving
Link to the StackOverflow much easier explanation & examples
There are a couple of ways of doing this (Some of which depends on how you are switching back and forth from each activity). It also depends on what things you are changing.
From your settings page, as you are changing different settings, you'll save this content within Preferences. (You can see more how to use Preferences here: https://examples.javacodegeeks.com/android/core/ui/settings/android-settings-example/ or by just Googling it).
On you main activity, depending on how you come back to it (onStart most likely), you can setup the things you need to programmatically.
So, you may need to do a little research on the Android lifecycle and how each cycle works (https://developer.android.com/guide/components/activities/activity-lifecycle.html), how to program the UI programmatically through Java (http://startandroid.ru/en/lessons/220-lesson-16-creating-layout-programmatically-layoutparams.html), and the Preferences Android library to save certain settings.
The xml isn't meant to be "altered". You can change the UI programmatically. It's possible to build an Android app without any xml. When Android was first built, it didn't use the xml to create the UI. It was all done through Java. It was then added to use xml to create your activities or fragments or any UI component. This made things easier for more static activities or activities with very little dynamic content.
I want to create a animation between two Activities with a image as shared element, see Customize Activity Transitions
My problem: In the source activity the image is drawn on a canvas of a custom view :-(
Is there a way to use this image as a shared element or do I have to add a real ImageView?
You can't share the image only, but you can share the entire custom view. This means that the entire custom View would disappear from the calling Activity when the shared element is transferred to the launched Activity. If your custom View only has the image, that would be fine, but if it paints other things, that would be disastrous.
If you want to share only the image, you'll have to create a View (e.g. ImageView) and move the image to it and then share it. That way, when the shared element is transferred, it hides properly from the calling activity.
The shared elements don't actually move Views between Activities, they just share a 'snapshot' of the view as a bitmap and the position of the view. In the launched activity, the view with the given transition name will be laid out in that position. You can use the snapshot or not, depending on your needs. By default, the snapshot is not used.
So you'll need some code like this:
public void launchActivity(final Intent intent, final CustomView view) {
final Bitmap bitmap = view.getSharedImage();
ImageView imageView = new ImageView(view.getContext());
imageView.setBitmap(bitmap);
LayoutParams layoutParams = view.createSharedImageLayoutParams();
final ViewGroup parent = (ViewGroup)view.getParent();
parent.addView(imageView, layoutParams);
parent.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
parent.getViewTreeObserver().removeOnPreDrawListener(this);
customView.hideSharedImage();
ActivityOptions activityOptions = ActivityOptions.
makeSceneTransitionAnimation(this, imageView, "destName");
startActivity(intent, activityOptions.toBundle();
}
});
setExitSharedElementCallback(new SharedElementCallback() {
#Override
public void onSharedElementsArrived(List<String> sharedElementNames,
List<View> sharedElements, OnSharedElementsReadyListener listener) {
super.onSharedElementsArrived(sharedElementNames, sharedElements,
listener);
parent.removeView(imageView);
customView.showSharedImage();
}
});
}
I haven't specifically tried the above, but that's the essence of it. If you don't want to use the newer onSharedElementsArrived, you can create a custom ImageView that listens for onVisibilityChanged. If you have an exit transition, you can also listen for the end of it as well. You just need some trigger that will tell you to reset the state so that the ImageView is removed and your custom View should draw the image again.
In the example above, I placed the ImageView in the same parent as the custom View. You may get more flexibility by putting it into the DecorView, but you'll have to figure out what the global position is and it will also overlay everything on the screen. Alternatively, since I added the ImageView to the parent, that won't work for all parents (e.g. ListView, LinearLayout). You know your View hierarchy and you'll have to choose the best place to put it.
Or, you could change your custom View to be a custom ViewGroup and contain the sharable image as an ImageView! Sounds easier to me.
This is a scaled down version of the actual problem. To recreate the difficulty I am facing.
I have taken the example from the official website developer.android.com to cite my problem.
Building a Flexible UI
MainActivity has 2 layouts. One is for the default(portrait in small screen devices) layout in the layout folder. The other layout for both large-screen and landscape mode, kept in layout-large and layout-land folder.
Default layout for activity_main.xml contains only one FrameLayout (R.id.fragment_container) in which I add and replace 2 fragments that I create, dynamically.
The other layout is same for both the layout-land and layout-large folders. It has 2 static fragments [R.id.headlines_fragment - to display a list of headlines] and [R.id.article_fragment - to display the details when headlines are selected]. Horizontally placed. One on the left to show the lists and the one on the right to show details.
This is the code for MainActivity.java which controls all the fragments :
public class MainActivity extends Activity implements OnHeadLineSelectedListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_article);
if(findViewById(R.id.fragment_container) != null) {
if(savedInstanceState != null) {
return;
}
HeadlinesFragment firstFragment = new HeadlinesFragment();
firstFragment.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(R.id.fragment_container, firstFragment).commit();
}
}
#Override
public void onArticleSelected(int position) {
ArticleFragment articleFrag = (ArticleFragment) getFragmentManager().findFragmentById(R.id.article_fragment);
if(articleFrag != null && articleFrag.isVisible()) {
articleFrag.updateArticleView(position);
} else {
articleFrag = new ArticleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction().replace(R.id.fragment_container, articleFrag);
transaction.addToBackStack(null);
transaction.commit();
}
}
}
As soon as the activity is started, I check if fragment_container that is the FrameLayout is present or not. If it is not present, then the layout with the 2 fragments has been loaded. Hence no need to add the fragments dynamicaly as they are already present.
Otherwise, I check if the savedInstanceState is null or not. If null, then I create a new HeadlinesFragment and add it to the frame. If it is not null, then it means that the activity has already been created previously, and hence the HeadlinesFragment must have been added already. No need to add it again. So, return.
And onArticleSelected() method replaces the existing fragment in the frame with the ArticleFragment, or if in the other layout, it simply updates the fragment as it is already present. It is called from the HeadlinesFragment when an item is selected.
Now, this all works perfectly well if we enter the activity in portrait mode and then change the orientation. No problem. Flawless.
But if we enter the activity in the landscape mode, as soon as I change the orientation to the portrait mode, a blank screen is shown.
The reason being, the onCreate() is called, and the savedInstanceState returns as not null. Hence, the HeadlinesFragment is not created and added to the frame.
And yes, if I remove that check, then the app works fine, but that will mean that a new HeadlinesFragment is created and added to the frame each time and gets stacked on top of eachother. Which is not at all desirable.
I cannot implement this by just finding out the orientation and applying the appropriate layout. Because, in large-screen devices, even if it is in portrait mode, it is supposed to show both the fragments at once.
I have tried many convoluted logic. But nothing seems to work. Any help is appreciated.
Entering activity in portrait mode
1> List Items are shown.
2> Clicking items replaces the fragment with the ArticleFragment (details).
3> Changing the orientation, shows both side by side. Everything works.
--->
Entering activity in landscape mode
1> Both the list and details are shown. Everything works.
2> But as soon as the orientation changes, you get the blank screen. As Headlines fragment is not created and added.
-->
It would be really helpful if someone could guide me as to how I can solve this problem. And as the actual project is huge, and this logic has already been implemented, a drastic change in logic is not an option anymore as that will mean re writting thousands of lines of code. Thank you. :)
Ok. I got the problem. The problem is we are using Fragments in xml layouts in large devices.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.android.fragments.HeadlinesFragment"
android:id="#+id/headlines_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.android.fragments.ArticleFragment"
android:id="#+id/article_fragment"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
So android is trying to catch the Fragments in savedInstanceState in MainActivity. When screen rotates, system tries to restore the above Fragments even though different layout loads in potrait mode. And so system considers that the article_fragment is also available on the right side and it tries to update it on click on the Headline.
So, What's the solution ?
I have changed a little code in MainActivity and nothing else :-)
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
setContentView(R.layout.news_articles);
// Check whether the activity is using the layout version with
// the fragment_container FrameLayout. If so, we must add the first fragment
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
/*if (savedInstanceState != null) {
return;
}*/
// Create an instance of ExampleFragment
HeadlinesFragment firstFragment = new HeadlinesFragment();
// In case this activity was started with special instructions from an Intent,
// pass the Intent's extras to the fragment as arguments
firstFragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
So what I have done just I told the system that I don't want any thing restored by using super.onCreate(null); so its restoring nothing now.
About the blank screen you are getting
Whenever you start activity in landscape mode. It loads large display by default. Without entering into if satement. Because it can't get fragment_container in landscape mode. And then you rotate screen to load portrait layout and system gets savedInstanceState != null and it returns without loading HeadlinesFragment. So you get bank screen.
So I have commented If statement as you can notice.
/*if (savedInstanceState != null) {
return;
}*/
So now It load everything correctly.
No issue
Download the code sample from this Developer site and modify it according to your needs.
Build Dynamic UI with fragments
Its super and reliable code.
I'm sure there are reasons why the Fragment was rendered correctly in Portrait -> Landscape and not in the reverse but one thing is very clear about the Activity lifecycle
Called when the activity is starting. This is where most
initialization should go: calling setContentView(int) to inflate the
activity's UI, using findViewById(int) to programmatically interact
with widgets in the UI, calling managedQuery(android.net.Uri,
String[], String, String[], String) to retrieve cursors for data
being displayed, etc.
Note the part that says This is where most initialization should go. By exiting after checking that savedInstanceState is null, you're leaving it up to the super class to restore your Fragment which is not a good idea considering the previous view was destroyed.
My advice, inspect the content of savedInstanceState instead of just checking if it is null. Ensure that it contains enough data to restore your previous state, if not initialize your Fragment.
The best practices with Fragments involve implementing all necessary methods to monitor the state.
I'm trying to save and retrieve data with SharedPreferences. Data stores and retrieved correctly, the problem is the app crashes on using this data to change a button text in a fragment
onCreateView() Function
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
SharedPreferences loadSettings = getActivity().getSharedPreferences("app_setts", Context.MODE_PRIVATE);
Button counterBtn = (Button) getActivity().findViewById(R.id.counterBtn);
int countInt = loadSettings.getInt("counter",0);
counterBtn.setText(String.valueOf(countInt));
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
return rootView;
}
So how to change the switch values using SharedPreference
*I have searched here but all gives crash
Sorry for bad english
Edit
I have found that i couldn't access the fragment component directly by onCreate() in MainActivity.java so i have to put my code in onCreateView() function
But there is a problem
The app runs and didn't crash but the value still won't change
Any idea to solve this ??
There's a couple of issues here.
The first is that you should probably move the code from your fragment's onCreateView() to onActivityCreated(), as that's when you are guaranteed that the Activity's onCreate has completed.
The second issue is that while this may work, it's poor design to have a fragment reach into and manipulate it's parent activity directly in this way.
The Android developer site has a good article about how to communicate between a fragment and it's activity via interfaces, at http://developer.android.com/training/basics/fragments/communicating.html
Personally, I prefer to decouple things even more, forego the interface, and use a message bus (e.g. Otto, EventBus, etc.) In this way, fragments are completely reusable / replaceable, and neither the activity nor the fragment need to know anything specific about each other - just which bus messages they emit / consume.
Logcat trace shows the 62nd line throws a NPE.
boolean onoffSwitch = loadSettings.getBoolean("appOnOff", false);
So, loadSettings must be null. You can print this by:
System.out.print(loadSettings);
So, you must try:
SharedPreferences loadSettings = getSharedPreferences("pref", Context.MODE_PRIVATE);
...
loadSettings.commit();
You may be try to get the shared preference value before save. First check you saving the values in shared preference..
I am in a peculiar situtation in my app.
When i app first loads there is a custom listview which is populated with data from the server.I am also using a class which contains different fields for the string data from the server.
When i click an item on the custom listview,the object of the corresponding class is passed onto the next fragment.
That is the current fragment is replaced with a new fragment and the object is passed with bundle.
Now a new listview loads with different tasks.On clicking a task a new fragment with a camera is loaded.
After taking the image and uploading to server, the status in the JSON changes to "COMPLETED".But now when i press back the old listview is shown.
Is there a way to populate the listview on back pressed with new data?
The issue is that I am passing an object right from the first fragment.
Now i need a new object on back pressed,how to pass the new object on back pressed?
When Fragment 2 gets the data, it should pass it along at some point before Fragment 1 is woken.
There are almost a half dozen ways to pass data, and the best way depends on a number of factors like who should own the lifecycle of the data, data pull vs push, dependency between fragments, do multiple components need updating, etc.
I'm just going to advise to simply cache the data on the activity until you learn more about the different methods.
//Fragment 2 puts data to activity
((MyActivity) getActivity).mListViewData = listViewData;
Then the next part of the question is how does fragment 1 get the data. Fragment 1 is hibernating on the backstack. When it wakes up it will call the onViewCreated() method (because it's previous view was destroyed before being placed on the backstack).
In that method, we check if there's new data waiting for Fragment 1.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
MyDataType listViewData = ((MyActivity) getActivity).mListViewData;
if(listViewData != null){
//setData is your own function for replacing the adapters
//data backing
listView.getAdapter().setData(listViewData);
}else{
listView.getAdapter().setData(...defaultData);
}
listView.getAdapter.notifyDataChanged();
}
Override the onBackPressed in the Activity that manages the Fragments. In it you can check if the fragment is visible or not (the one from which an action should be performed if the back is pressed) and then execute your action.
#Override
public void onBackPressed(){
Fragment myFragment = getFragmentManager().findFragmentByTag("MY_FRAGMENT");
if (myFragment.isVisible()) {
String json = myFragment.getJsonData(); //update it locally
if(isUpdated){
Fragment listFragment = getFragmentManager().findFragmentByTag("MY_LIST_FRAGMENT");
listFragment.updateListView(json); //Add this method on your fragment
}
}
super.onBackPressed();
}
Obs.: To use the .findFragmentByTag() you should add tags once you're making the transaction like so:
fragTrans.replace(android.R.id.content, myFragment, "MY_FRAGMENT");
If, for any reason the listFragment has been cleaned from memory, you would have to reload the data anyway so just download the new data again.
To update the ListView please see: How to refresh Android listview? . Note thought that you will need to will need to send a new data set to the list view (which you can do inside the updateListView() method)