Android: Keep fragment running in the background - java

I have a fragment that starts a count and changes an icon status. After opening the app there is a count - 00:00 and a button that says START.
After clicking START the counts starts and the button changes to STOP.
After clicking STOP the count stops and the button changes to START. Pretty basic.
The thing is that after clicking START and minimalizing the app and opening it back (putting app in background and back) the count and button is always reverted back to START and 00:00.
So the question is: How can I keep the fragment alive after minimalizing the app?
Code:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View recordView = inflater.inflate(R.layout.fragment_record, container, false);
ButterKnife.bind(this, recordView);
return recordView;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
btnPause.setVisibility(View.GONE);
recordBtn.setColorPressed(getResources().getColor(R.color.colorPrimary));
}
#OnClick(R.id.btnRecord)
public void recordAudio(){
onRecord(mStartRecording);
mStartRecording = !mStartRecording;
}
private void onRecord(boolean start) {
Intent intent = new Intent(getActivity(), RecordingService.class);
if(start){
recordBtn.setImageResource(R.drawable.ic_media_stop);
//Toast.makeText(getContext(), "Started recording", Toast.LENGTH_LONG).show();
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
getActivity().startService(intent);
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
recordingStatusTxt.setText("Recording now...");
} else {
recordBtn.setImageResource(R.drawable.ic_mic_white);
chronometer.stop();
chronometer.setBase(SystemClock.elapsedRealtime());
timeWhenPaused = 0;
getActivity().stopService(intent);
recordingStatusTxt.setText("Click the button to start recording");
}
}

You cannot keep your fragment alive.
System handles it itself. It can kill it anytime it wants to.
The thing is that after clicking START and minimalizing the app and
opening it back (putting app in background and back) the count and
button is always reverted back to START and 00:00.
The reason behind it actually pretty simple - your fragment basically recreates. And this is an expected behaviour. To continue you should learn Activity and Fragment lifecycle
And as I understand this is a Recorder app so you should get time elapsed from that service.
For example, you could Override onResume() method of your Fragment with setting time and button status using information gained from your service.
Good luck!

If you minimize the app while running, the android operating system can terminate your app and restart it when the user returns to it. You cannot guarantee it will remain in memory. If there is any data that needs to be maintained, you are responsible for saving it in the state handlers.
If you wish to have some process continue in the background, you'll need to start an Android Service and your fragment will need to communicate with it. The Fragment is just the UI piece of the app.

Related

LiveData observer's onchanged() method executes several times

I am new to the android development. Currently i am working with the architecture components. When I inflate my fragment for the first time the Observer onChanged() executed several times so the data in my ui got repeated. But it is working fine when I detach and attach the fragment.
MyFragment.java
private MyViewModel myViewModel;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup
container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.my_fragment, container, false);
return rootView;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
myViewModel = ViewModelProviders.of(this).get(MealViewModel.class);
myViewModel.getBreakfast(randomNum).observe(getViewLifecycleOwner(), new Observer<List<BreakFast>>() {
#Override
public void onChanged(#Nullable final List<BreakFast> breakFasts) {
for (int i = 0; i < breakFasts.size(); i++) {
String food = breakFasts.get(i).getFood();
if (food.contains("||")) {
food = food.replace("||", "\n");
}
builder.append( food + "\n");
}
resultTextView.setText(builder.toString());
}
});
}
In the above code, the onChanged method gets executed many times when the view inflates for the first time.
LiveData is a type of observable. Its job is to publish changes when data hosted in it gets updated (either manually by calling postValue on it or automatically when new data is added)
If you want to get data only once, then LiveData is not an option for you. You can simply use Single to achieve this.
Another thing that you can do is - simply ignore changes when they are pushed to you second time onward through your onChanged() method. What I mean by this is, do not update your text view second time onward. However, you should really have a separation logic that's clean enough to distinguish in between the callback that you will use and other callbacks that you won't use.
One last thing that I can think of is remove observer - your current lifecycle owner (activity or fragment) once you have observed the changes once. By calling removeObserver() method on LiveData.
PS: All of these are just workarounds. You should really rethink about using LiveData if live changes to the data is not something that you wanted to begin with.

Android switching between gl views causes app freeze

I'm still learning how to program in Android and I created an Activity with a start view:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
mMainView = (RajawaliSurfaceView) findViewById(R.id.my_view);
mMainView.setSurfaceRenderer(new Renderer());
mMainView.setZOrderOnTop(false);
// Also set up a surface view I'd like to switch on back and forth
mGLView = new GLSurfaceView(this);
mGLView.setEGLContextClientVersion(2);
mGLView.setDebugFlags(GLSurfaceView.DEBUG_CHECK_GL_ERROR);
mGLView.setRenderer(cameraRenderer_ = new GLRenderer(this));
mGLView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
This works and I can even switch to the GL view seamlessly:
public void switchToGLView(View view) {
setContentView(mGLView);
}
But after I'm done with the GL thing, I have the GLRenderer call a restore function in my main activity
public void restoreRenderer() {
// Restoring the previous view (my_view) with the non-GL renderer
setContentView(mMainView); <<<<<< FREEZES HERE (blackscreen)
}
and unfortunately this never works: the screen remains black from the previous GL renderer and by debugging the setContentView line I discovered that:
I don't get ANY error or warning log in the Android Monitor
The execution NEVER continues. It gets stuck in there forever
Any tips or hints on how to solve this?
You could solve this problem using a Frame or Relative layout.
If I understand correctly, both mMainView and mGLView are fullscreen.
There are a few options:
You could change the visibility of the views:
public void switchViews() {
if(mMainView.getVisibility() == View.VISIBLE){
mMainView.setVisibility(View.GONE);
mGLView.setVisibility(View.VISIBLE);
} else{
mMainView.setVisibility(View.VISIBLE);
mGLView.setVisibility(View.GONE);
}
}
The other option is to change the Z order of the views:
public void switchViews(View view){
view.bringToFront();
view.invalidate();
}
Unless there are any drawbacks you encountered using the above methods.

Android multi-fragment layout in landscape mode

Although I got my code to work, I now have no idea what it's actually doing.
My app is an RSS reader, and the main content is in a Fragment containing a ListView with NewsStory objects. When a list item is clicked, it opens an Intent with the website linked from the RSS.
Now the problem is, I don't understand the Intent here, it's not the way I've ever used Intents before.
Also, I have to make it so that when I change the orientation, the original profile Fragment takes up the left half of the screen and the linked webpage takes up the right half of the screen. I've tinkered around with it, to no avail. I did a bit of research on orientation changes, but I feel like doing things with Fragments always changes how everything works. Anyway, here's the Fragment code. Any thoughts would be greatly appreciated.
public class HeadlineFragment extends Fragment {
EditText input;
Button search;
ListView headlines;
NewsDataSource ds;
public HeadlineFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_headline,container,false);
input = (EditText)v.findViewById(R.id.txtInput);
search = (Button)v.findViewById(R.id.btnSearch);
headlines = (ListView)v.findViewById(R.id.listView);
try {
ds = new NewsDataSource();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
headlines.setAdapter(new NewsDataSourceAdapter(this.getActivity(), ds));
headlines.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent,View view, int position, long id)
{
String url = NewsDataSource.stories[position].getLink();
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
}
});
return v;
}
/*
#Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
}
*/
}
Firstly, regarding that Intent, its an implicit Intent. When you set the action as ACTION_VIEW and add a URI / URL as an extra, the OS gets the message that you want to open an app that can navigate to that URI / URL.
Secondly, for showing a two-pane layout in landscape mode, you'll have to show that RSS content in a Fragment instead of an Activity as you are currently doing, and you'll have to display those Fragments side-by-side in an Activity in landscape mode. See the Retrieving a List of Contacts example for a really good explanation of how to display a multi-pane master detail layout in portrait mode.
References:
1. Intents and Intent Filters.
2. Planning for Multiple Touchscreen Sizes.

How to use the EventBus onEvent method?

I'm using EventBus in my Android application. In my mainActivity, I have this handler method which sends live data to the EventBus as follows:
private final Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case TGDevice.MSG_STATE_CHANGE:
EventBus.getDefault().postSticky(msg.arg1);
...
I'm using Fragments class and I need to receive the message from the handler.
I have registered the Fragment class in the onCreateView method as follows:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_data_log, container, false);
EventBus.getDefault().register(this);
tv = (TextView) view.findViewById(R.id.tv);
}
public void onEvent(Message message){
tv.setText("Signal" + message);
}
And I have the onEvent method which is suppose to be called when there is an Event. Unfortunately, this method is never called. I thought it might be method to be overridden but it doesn't seem to be.
What do i need to do to read from messages from the EventBus?
Also, in debugging modes, where can I see the number of threads being created? (I'm using Android Studio)
Unfortunately, this method is never called
That is because your onEvent() takes a Message, and (presumably) you are not posting a Message. You are posting whatever arg1 is.
Also, in debugging modes, where can I see the number of threads being created? (I'm using Android Studio)
Go into the Android Debug Monitor (Tools > Android > Android Debug Monitor from the main menu), and there's a threads view in DDMS inside of there.

onBackPressed() not working while coming from another activity

I have three activities: First, Second and Third. I used this method in Second activity:
public void onBackPressed() {
super.onBackPressed();
finish();
}
and this on Third activity:
public void onBackPressed() {
super.onBackPressed();
Intent i = new Intent(Third.this, Second.class);
startActivity(i);
finish();
}
The problem is when I press back button after coming from the Third activity, I am going into First activity instead of finish(). I am successfully exiting the application when I click back button right after coming from first activity but not after coming from Third activity.
How to solve this problem?
EDIT: Thanks for the answers guys,the answer of "Ved Prakash" solved the problem for me.But i have a weird problem now.When i press back button the app is successfully exiting but the app which i minimized to Recent Apps button is coming on to the screen and exiting.For example,if i have opened Setting app before opening my app,when i press back button,my app is exiting and immediately Settings app is also opening and exiting itself.What might be the problem?
Your problem is that you don't seem to understand how Activities work. The finish() function ends the current Activity, and then you receive the previous Activity from the backstack.
My recommendation is that you should use a single Activity, and hold Fragments inside it. If you want it so that pressing the Back button ends the application at any screen that is displayed, you could do the following:
Activity XML:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/initial_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Activity that holds the Fragments:
public class InitialActivity extends FragmentActivity implements ReplaceWith
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_initial);
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener()
{
public void onBackStackChanged()
{
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount == 0)
{
finish();
}
}
});
if (savedInstanceState == null)
{
getSupportFragmentManager().beginTransaction().add(R.id.initial_container, new FirstFragment()).commit();
}
}
#Override
public void replaceWith(Fragment fragment)
{
getSupportFragmentManager().beginTransaction().replace(R.id.initial_container, fragment).commit();
}
}
Example for a Fragment:
public class FirstFragment extends Fragment implements View.OnClickListener
{
private ReplaceWith activity_replaceWith;
private ImageView exampleImage;
public FirstFragment()
{
super();
}
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
try
{
activity_replaceWith = (ReplaceWith) activity;
}
catch (ClassCastException e)
{
Log.e(getClass().getSimpleName(), "Activity of " + getClass().getSimpleName() + "must implement ReplaceWith interface!", e);
throw e;
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_first, container, false);
exampleImage = (ImageView) rootView.findViewById(R.id.fragment_first_example_image);
exampleImage.setOnClickListener(this);
return rootView;
}
#Override
public void onClick(View v)
{
if(v == exampleImage)
{
activity_replaceWith.replaceWith(new SecondFragment());
//please note that this should be done only if you are planning
//only on single-screen applications
//with no other layouts based on orientation or size
//otherwise, the Activity needs to be responsible for this, not the Fragment
}
}
}
This way, when you press the Back button, your application would end from any displayed screen.
Ok your code is wrong.
If you will look at activity source, you see that activity.onBackPressed() is calling finish(). So if call super.onBackPressed() you don't need to call finish.
Finish() is not stopping your application, it's stopping current activity.
Your code on third activity very strange. You are trying to stop activity and start another same activity.
What exactly you want to achieve?
If you want to exit application from your third activity, you need to clear your backstack. But I think you have problem with structure of your app.
Ok. then you should finish your first activity when you go to second activity like this(If you are using intent for that):
Intent it=new Intent(FirstActivity.this,SecondActivity.class);
finish();
startactivity(it);
and same for Second Activity:
Intent it=new Intent(SecondActivity.this,ThirdActivity.class);
finish();
startactivity(it);
this done your work...when you are in third activity the above activities are finished..
and when you press backButton you will be exit from application..
Good luck.
You can use -
public static final int FLAG_ACTIVITY_CLEAR_TOP
If set, and the activity being launched is already running in the
current task, then instead of launching a new instance of that
activity, all of the other activities on top of it will be closed and
this Intent will be delivered to the (now on top) old activity as a
new Intent.
And here is how -
When the user wishes to exit all open activities, they should press a button which loads the first Activity that runs when your app starts, in my case "MainActivity".
Intent intent = new Intent(getApplicationContext(), LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("EXIT", true);
startActivity(intent);
The above code clears all the activities except for LoginActivity. LoginActivity is the first activity that is brought up when the user runs the program. Then put this code inside the LoginActivity's onCreate, to signal when it should self destruct when the 'Exit' message is passed.
if (getIntent().getBooleanExtra("EXIT", false)) {
finish();
}
This explanation part is also introduced at exit-an-android-app.

Categories

Resources