how to receive data from service when fragment is resumed? - java

I have a side menu drawer in my activity that has 2 options ("My files" and "Sync"), each of which is a fragment. When I am in "Sync" fragment, there is a button to start downloading files from the server. This is done through an intent service which is running in the background. I use a result receiver in my fragment which keeps getting the download progress (%) from the service and displays it in the fragment.
The problem is that if I switch to the "My Files" fragment while the download is going on and come back to the "Sync" fragment, the view is reset and the progress is lost. The service keeps running in the background but the fragment does not show the progress.
My question is that when I switch the fragment, does the "Sync" fragment still receive the progress from the service that keeps running in the background. How do I start receiving the progress updates from the service when I go back to the "Sync" fragment.
Below is the code in the fragment that starts the service.
intent.putExtra("link", downloadLink);
syncReceiver = new SyncReceiver(new Handler());
intent.putExtra("result_receiver", syncReceiver);
getContext().startService(intent);
Code in the service that sends the download progress to the fragment.
resultReceiver = intent.getParcelableExtra("result_receiver");
link = intent.getStringExtra("link");
while ((bytesRead = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, bytesRead);
byteCount += bytesRead;
String kbs = String.valueOf(byteCount / 1024);
bundle = new Bundle();
bundle.putString("response", kbs);
bundle.putString("total_data", total_file_size);
resultReceiver.send(CONTENT_PROGRESS, bundle);
}
The progress receiving code in the fragment.
public class SyncReceiver extends ResultReceiver {
private static final int CONTENT_PROGRESS = 2;
public SyncReceiver(Handler handler) {
super(handler);
}
public void onReceiveResult(int resultCode, Bundle data) {
super.onReceiveResult(resultCode, data);
String response = data.getString("response");
if (resultCode == CONTENT_PROGRESS) {
updateContentProgress(response, data.getString("total_file_size");
}
}
}
private void updateContentProgress(String progress, String total_file_size) {
double current = Double.parseDouble(progress);
double totalData = 0;
totalData = Double.parseDouble(total_file_size);
String percent = String.valueOf((current / totalData) * 100);
status.setText(R.string.download_progress);
status.append(" " + percent + "%");
}

First move your classes that do the downloading into you Activity. Then from You fragment call into the Classes to to check on status of Your Download with Snippet below...Since activity will still hold reference to your Initial Download you will be able to Track progress. When you switch fragments your activity automatically kills reference to download thread since fragment is removed. Keeping reference in activity allows you to track across multiple fragments.Make sure you classes are public.
((YourBaseActivityWithProperClasses) getActivity()).trackDownloadProgress();

The Fragment won't get updates since it might get destroyed and recreated.
I had a similar problem and my solution was to have an extra background-Fragment to keep the ResultReceiver, that doesn't get destroyed by setting setRetainInstance(true).
An explanation and a possible solution can be found here: https://stanmots.blogspot.com/2016/10/androids-bad-company-intentservice.html
Another good read concerning this problem:
https://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
So my solution was to hold the ResultReceiver inside an extra Fragment having setRetainInstance(true).
To get the right state when (re-)creating the View, I do the following in my onCreate():
final FragmentManager manager = ((Activity) getContext()).getFragmentManager();
// Try to find the Fragment by tag
final IntentObserverFragment intentObserverFragment =
(IntentObserverFragment) manager.findFragmentByTag(IntentObserverFragment.TAG);
if (intentObserverFragment == null) {
// Service is not active
this.progressBar.setVisibility(View.GONE);
} else {
// Service is working - show ProgressBar or else
this.progressBar.setVisibility(View.VISIBLE);
// Stay informed and get the result when it's available
intentObserverFragment.setCallbackClass( this );
}
In my IntentObserverFragment I start the work at onAttach() - not at onCreate(), because the needed Context isn't available yet, which would result in a NPE using e.g. getActivity()!
#Override
public void onAttach(Context context) {
super.onAttach(context);
final MyResultReceiver myResultReceiver = new MyResultReceiver();
final Intent intent = new Intent( context, MyIntentService.class );
intent.putExtra(MyIntentService.BUNDLE_KEY_RECEIVER, myResultReceiver);
context.startService(intent);
}

Related

How to keep updating recyclerview in the next activity by the data flow sent by BLE device in current activity?

I am completely new to Android and just learned Object-oriented programming. My project requires me to build something on open-source code. I am really struggling with this special case. Two fragments are under activity_main, one of them is TerminalFragment. If TerminalFragment is active under activity_main, data from a BLE device will keep flowing into activity_main. Is it possible to keep passing the data to the next activity without clicking a button (in this case, menuItem(R.id.plot))? I added a recyclerview on activity_main2 and want it display the data which is flowing in activity_main.
In my testing with my current method, the recycler view won't update itself, it just captures the data in the recyclerview of activity_main with TerminalFragment once the user clicked the menuItem (id,plot). What kind of thing I need to add in my method? Should I create another Fragment instead of activity_main2? As I looked this up in the internet, it seems not possible to work that way between a fragment and next activity. Much Appreciated.
onOptionsItemSelected of TerminalFragment
if (id == R.id.plot){
Intent intent = new Intent(getActivity(), MainActivity2.class);
intent.putExtra("output",output); //output
startActivity(intent);
return true;
}
receive() of TerminalFragment
private void receive(byte[] data) {
if(hexEnabled) {
receiveText.append("Hello" + TextUtil.toHexString(data) + '\n');
} else {
String msg = new String(data);
if(newline.equals(TextUtil.newline_crlf) && msg.length() > 0) {
// don't show CR as ^M if directly before LF
msg = msg.replace(TextUtil.newline_crlf, TextUtil.newline_lf);
// special handling if CR and LF come in separate fragments
if (pendingNewline && msg.charAt(0) == '\n') {
Editable edt = receiveText.getEditableText();
if (edt != null && edt.length() > 1)
edt.replace(edt.length() - 2, edt.length(), "");
}
pendingNewline = msg.charAt(msg.length() - 1) == '\r';
}
receiveText.append(TextUtil.toCaretString(msg, newline.length() != 0)); //print out data
output = receiveText.getText().toString(); // CharSequence to String
}
}
OnCreate of mainActivity2, *receiveData() is to add data to the recyclerview
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Toolbar toolbar = findViewById(R.id.toolbar2);
setSupportActionBar(toolbar);
recyclerView = findViewById(R.id.dataflow);
dataList = new ArrayList<>();
String data;
Bundle extras = getIntent().getExtras();
if (extras != null)
{
data = extras.getString("output");
receiveData(data);
}
setAdapter();
}
i think your problems is unknown how to get activity_main data onto activity_main2 dynamically , You can user " EventBus " to solve data updates dynamically , whatever you use another fragment or activity_main2 .

Parsing the memory location of the source file of an ImageButton causes an error, why?

The ERROR I cannot justify
Say we have the memory location of a drawable (I stored that in a tag in the XML file) and we use it as a parameter for setImageResource(MEMORY_LOCATION). I can't make it work as the function is waiting for an int as input and by the time I parse it... I get an error
What I mean by memory location
The exact point I do not understand is Why does parsing a string representing a memory location to an int CAUSES AN ERROR?
If you want to find out why I want this or can suggest another way of solving the problem please read the full description
The problem I am trying to solve is to Find source from id
Say we have n ImageButtons and we want to pass their drawable to the next Activity.
One possible way would be using multiple case elements...
public class MainActivity extends Activity {
public static int chosen_button;
private ImageButton image_Button_1;
private ImageButton image_Button_2;
...
private ImageButton image_Button_n;
private final int possible_Buttons[] = {R.drawable.imgbtn1,R.drawable.imgbtn2,...,R.drawable.imgbtnn};
private View.OnClickListener tweakedOnClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
switch(v.getId())
{
case R.id.imgbtn1:
chosen_Button = possible_Buttons[0];
break;
case R.id.imgbtn2:
chosen_Button = possible_Buttons[1];
break;
...
case R.id.imgbtnn:
chosen_Button = possible_Buttons[n];
break;
default:
throw new RuntimeException("Unknown button ID");
}
Intent activity_Main_To_Secondary = new Intent(MainActivity.this, SecondaryActivity.class);
startActivity(activity_Main_To_Secondary);
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
image_Button_1 = findViewById(R.id.imgbtn1);
image_Button_2 = findViewById(R.id.imgbtn2);
image_Button_n = findViewById(R.id.imgbtnn);
image_Button_1.setOnClickListener(tweakedOnClickListener);
image_Button_2.setOnClickListener(tweakedOnClickListener);
image_Button_n.setOnClickListener(tweakedOnClickListener);
}
}
Then in order to get the drawable (as the ImageButton is only wired to the MainActivity via the XML file and is not wired to the SecondaryActivity at all) we can simply call the public int chosen_button and it works!
But with multiple case statements and multiple lines of code...
Another approach would be:
v.getId() gives the id, a memory location (I guess pointing to the XML file?)
R.drawable.triangle gives the src, a memory location pointing to the png file.
So as there is no function returning the memory location of the drawable by taking the id in, I tried setting the tag in the XML section file in the MainActivity to the memory location of each drawable. Looks like this:
<ImageButton
android:id="#+id/imgbtn1"
...
android:tag="‭2130903048"
<!-- Does it make any difference if it is written in hex, binary or decimal??? -->
android:src="#drawable/imgbtn1"
Everything works from when you try to v.getTag().toString() in MainActivity but by the time I try to parse it to an Int (or putExtra to the intent linking main and secondary activities) then the app breaks leading to the question of my post.
Intent activity_Main_To_Draw = getIntent();
String mem_location = activity_Main_To_Secondary.getStringExtra("$mem_loc");
Integer.parseInt(mem_location)

How to run the same fragment several times?

I'm creating an app that will allow the user to take little tests. There is therefore a TestActivity made up by a PreTestFragment (with a button "Start Test"), a TestFragment and a PostTestFragment (with results of the test). As TestFragment will be the fragment hosting a single question I want it to be displayed several times (let's say 10).
All I could come up with was making a static counter that is incremented every time the user presses NEXT in a TestFragment and that makes him quit after the counter reached 10.
I'm new to fragment management, so I feel like I may have done several errors in the implementation of the onClick() of the NEXT button.
static private int current_question;
public void onClick(View view) {
FragmentManager mng = getFragmentManager();
FragmentTransaction fgmTr = mng.beginTransaction();
Fragment currentFgm = mng.findFragmentByTag(FragmentTags.TEST_FTAG.toString());
Fragment nextFgm;
Bundle bundle = currentFgm.getArguments();
if (current_question < 10) { // test still running
current_question++;
nextFgm = mng.findFragmentByTag(FragmentTags.TEST_FTAG.toString());
// ...
// putting needed data in the bundle
// ...
if (nextFgm == null)
nextFgm = new TestFragment();
} else { // test is finished
nextFgm = mng.findFragmentByTag(FragmentTags.POST_FTAG.toString());
// ...
// putting different needed data in the bundle
// ...
if (nextFgm == null)
nextFgm = new PostTestFragment();
}
nextFgm.setArguments(bundle);
fgmTr.replace(R.id.test_body, nextFgm, FragmentTags.POST_FTAG.toString());
fgmTr.addToBackStack(null);
fgmTr.commit();
}
#Override
public void setArguments(Bundle args) {
super.setArguments(args);
// Should I use if-else here too to pass different data in the bundle?
}
Running it and pressing "NEXT" the first time I get the following error:
java.lang.IllegalStateException: Fragment already active
How to fix this code?
If your fragment (nextFgm) has already been created, you can't call setArguments.
take a look:
https://developer.android.com/reference/android/app/Fragment.html#setArguments%28android.os.Bundle%29
Usually i did it by create a new fragment's instance every time:
android.support.v4.app.FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame, new myFragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();

How to pass information from a parent Activity into a RUNNING Fragment?

I understand that Fragments can receive information from the parent Activity through methods like onAttach() and implementations of Listeners, but those approaches involve passing information exclusively at the conception of each Fragment. I'm wondering how to change information in a Fragment from an Activity while the Fragment is running; for instance, in my case, I have a Navigation Drawer with check items as my parent navigation Activity. The checks correspond to which items of an ArrayList should be displayed in the Fragment.
How can I make my Fragment's ArrayList immediately responsive to changes in the parent Activity without recreating the Fragment?
#Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, final int childPosition, long id) {
checkBox = (CheckBox) v.findViewById(R.id.show_child_subject_checkBox);
checkBox.toggle();
tempSI = SubjectInfo.findById(SubjectInfo.class,
childItem.get(groupPosition).get(childPosition).getId());
onSubjectCheck(tempSI);
tempSI.subjectChecked = !tempSI.subjectChecked;
tempSI.save();
childItem.get(groupPosition).set(childPosition, tempSI);
myAdapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "Toggled " +groupPosition + "|" + childPosition,
Toast.LENGTH_SHORT).show();
// the oncheckedchangedListener now resides and operates out of adapter
// in a much more efficient, clean manner
return true;
}
onSubjectCheck is the implementation of the Listener in the Activity. Perhaps this is not the best way to do this, but I figured I'd show how I toyed around with the idea, at least.
#Override
public void onSubjectCheck(SubjectInfo si) {
for (int go = 0; go < subjectList.size(); go++) {
if (subjectList.get(go).equals(si)) {
si = new SubjectInfo(si.subjectName, si.itemHeaderTitle, si.subjectGrade,
si.subjectArchived, !si.subjectChecked);
amFragment.subjectList.set(go, si);
}
}
amFragment.sorterAndFilter(false);
}
There are many ways you can pass information from a parent Activity to a fragment.
Using setArguments on Fragment.
Fragment mFragment = new MyFragment();
Bundle args = new Bundle();
args.put**(KEY,value);
mFragment.setArguments(args);
Using fragment instance directly.
Inside Activity,
mFragment = getFragmentManager().findFragmentById(id) or findFragmentByTag(tag);
mFragment.methodCall();

Fragment listview update from activity

I am pretty new to Android (3 days), but I have a pretty good background in PHP (which may be the cause of my confusion in a Java based environment). I started building an Android app using Android Studio (Beta). I created the default Android Studio activity with the Navigation Drawer Activity. I edited the activity fragment part to look like this:
#Override
public void onNavigationDrawerItemSelected(int position) {
Bundle bundle = new Bundle();
bundle.putStringArrayList("contacts", arr);
bundle.putStringArrayList("messages", messages);
Fragment fragment = null;
switch (position) {
case 0:
fragment = new FriendsFragment();
break;
case 1:
fragment = new ChatsFragment();
break;
case 2:
fragment = new GroupsFragment();
break;
case 3:
fragment = new LogoutFragment();
break;
default:
break;
}
if (fragment != null) {
fragment.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, fragment).addToBackStack(null).commit();
}
}
As you can see I am passing a Bundle to my Fragments called "messages" and "contacts" when an item is selected in the Navigation Drawer. The "messages" bundle are XMPP messages received by the aSmack library from an OpenFire server. So basically I'm trying to create a XMPP client. When I run the app I can receive the messages in the "ChatsFragment".
Now my problem:
I have to press the "ChatsFragment" item on the drawer to have my messages updated (re-receive the bundle) everytime I feel like there are new messages received from the server. But I want this to be done automatically by the fragment.
First I would like to know if my procedure is correct (Activity listens to server, creates bundle, send bundle to fragment, bundle updates messages on receive**)
** = This part I haven't been able to understand how to implement.
1- If the procedure is correct tell me how I should get the messages to be updated by the fragment through the activity?
2- If this is not the correct way to do things in Android, recommend me a way of doing it.
My code for displaying the messages in fragment:
private void displayListView() {
// Messages array list
List<String> contacts = getArguments().getStringArrayList("messages");
//System.out.println("arr: " + contacts);
//create an ArrayAdaptar from the String Array
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(getActivity(),
R.layout.url_list, contacts);
ListView listView = (ListView) getView().findViewById(R.id.listView);
// Assign adapter to ListView
listView.setAdapter(dataAdapter);
//enables filtering for the contents of the given ListView
listView.setTextFilterEnabled(true);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// Send the URL to the host activity
//mListener.onURLSelected(((TextView) view).getText().toString());
}
});
}
Thanks in advance.
Typically for long running operations in the background, like listening to a server, incoming messages, etc, you need to use Services. You do so by subclassing the Service class in Android.
Now for your problem - the design approach should be that you have a background service listening to incoming messages. Anytime a message is received (an input stream in your socket operator) you should send a "broadcast" an intent that a message was received. A custom broadcast receiver that you create should wait for this broadcast. Within the onReceive() method of this receiver, you should trigger the creation of the bundle and updating your message.
Remember you should always delegate your long running operations in Android to services. That is exactly what they are for.
So basically if you're already listening for new messages to come in your activity, then you must have some kind of callback like onMessageRecieved() or something like that.
If you do, you can then notify your fragment in this way.
Create a field (goes under your class declaration) called curFrag, so something like this:
public class MyActivity extends Activity {
private Fragment curFrag;
//other code...
}
then in the code you posted you would initialize the curFrag there, but you also need to set a tag for the current fragment. This will be based on your case statement. Make some final string variables as tags.
i.e.
public static final String CHATSTAG = "chatstag";
#Override
public void onNavigationDrawerItemSelected(int position) {
Bundle bundle = new Bundle();
bundle.putStringArrayList("contacts", arr);
bundle.putStringArrayList("messages", messages);
Fragment fragment = null;
String tag = null;
switch (position) {
case 0:
tag = FRIENDSTAG;
fragment = new FriendsFragment();
break;
case 1:
tag = CHATSTAG;... and so on through the switch statement.
fragment = new ChatsFragment();
break;
case 2:
fragment = new GroupsFragment();
break;
case 3:
fragment = new LogoutFragment();
break;
default:
break;
}
if (fragment != null) {
fragment.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
//remember to set the tag.
if(tag != null) {
fragmentManager.beginTransaction()
.replace(R.id.container, fragment, tag).addToBackStack(null).commit();
} else {
fragmentManager.beginTransaction()
.replace(R.id.container,fragment,DEFAULTTAG).addToBackStack(null).commit();
}
//new code
curFrag = fragment;
}
}
Now in your activity when a new message comes in, check the tag of the fragment and then if it matches a certain fragment, notify the fragment that new data has come in and retrieve it in the fragment.
public void onMessageRecieved() {
if(curFrag.getTag().equalsIgnoreCase(CHATSTAG)) {
ChatsFragment frag = (ChatsFragment) curFrag;
frag.notifyDataRecieved();
}
}
Once you have a reference to your fragment in the activity, you have access to any public methods in that fragment.
If your fragment cannot access the data on its own, then you'll need to get a reference to the activity and create a method in the activity that returns the new data.
So in your activity:
public String getMessageData() {
String newData = ...//get stuff from server;
return newData;
}
then in your fragment
public void notifyNewMessage() {
try {
MyActivity activity = (MyActivity) getActivity();
} catch (ClassCastException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
String message = activity.getMessageData();
//do something with the message.
}
It's not necessarily pretty but it works pretty well. You should also check to make sure your fragments are attached when you do this so that you avoid null exceptions.
Hope this helps!

Categories

Resources