ANDROID: Update data in another activity in Master Detail Flow Layout - java

I've searched for a solution for my question all over the internet but I haven't been able to find one and I hope you can help me out
I am trying to create a master detail flow application in android with 2 activities and the second activity contains a fragment. Can anyone please tell me how I can simultaneously update the value in the MainActivity() when I make a change in the fragment's EditText field? I have tried using an Intent but when the 2 activities are side by side that doesnt seem to work well.
Screenshot of Emulator
Any suggestions?

It seems you are in a context as follows:
When A happens, it triggers B
As a result, I suggest you to use EventBus library in your project.
The installation is easy. First, add the following code in your build.gradle file:
compile 'org.greenrobot:eventbus:3.0.0'
Second, let's see what we are going to add in our codes.
In the Fragment which you wanted to make changes:
/* When A happens */
myButton.setOnClickListener(new View.OnClickListener() { // complete entering the content, update it
EventBus.getDefault.post(MyUpdateEvent(myContent));
});
Create your custom class MyUpdateEven:
public class MyUpdateEvent{
private String myContent;
public MyUpdateEvent(String myContent) {
this.myContent = myContent;
}
public String getUpdateContent() {
return myContent;
}
}
In the Activity you wanted to update:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventBus.getDefault.register(this); // add this code to monitor the update
}
/* It triggers B */
#Subscribe // don't forget to add #Subscribe
public void onEvent(MyUpdateEvent event){
// this is your custom method
myTextView.setText(event.getUpdateContent()); // do your update
}
#Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault.unregister(this); // when you leave this lifecycle, cancel the monitoring
}
}
EventBus is a good library that I've been used a lot in my projects.
I think it can solve your problem.

Try to define a interface.
public interface OnEditActivity {
public void onEdit(ActivityObject activityObject, boolean isEditing);
}
And on your another class for example DetailActivity, then you have to override the method onEdit that you created in your interface:
public class DetailActivity extends AppCompatActivity implements OnEditActivity{
//IN HERE --- Create method.
#Override
public void onEdit(ActivityObject activityObject, boolean isEditing) {
if(isEditing){
displayView(activityObject,true);
}else{
displayView(activityObject,false);
}
}
}
And in your EditFragment for example will look like this:
public class EditFragment extends Fragment{
//Define your interface in your fragment
private OnEditActivity onEditActivity;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_edit_activity, container, false);
return v;
}
public void onAttach(Activity a) {
super.onAttach(a);
onEditActivity =(OnEditActivity) a;
}
}
And if you want to call onEdit method just call:
onEditActivity.onEdit(activityObjectNew,false)
I hope this help you !

Related

Modify elements of a layout from another class

I am a student and a beginner in java and android.
I am modifying an android application which allows to configure bluetooth beacons. With the original application we can only configure the beacons one by one, therefore I want to modify the application to be able to configure a good number automatically.
I have a problem when I want to modify an element in the layout of a class from another class.
From the Main class I can interact well with the elements of the Main layout. But since the Main class, I can't interact with other layouts (PasswordDialog in my case). I've been struggling for several days, I tried responses to similar posts but without success because the configuration of my classes is quite special and I don't want to modify them too much so as not to alter the functioning of the application. If anyone has any leads, I would be very grateful;)
The main class:
public class MainActivity extends BaseActivity implements RadioGroup.OnCheckedChangeListener, MokoScanDeviceCallback, AdapterView.OnItemClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ...
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// ...
final PasswordDialog dialog = new PasswordDialog(this); // Another function which already calls the PasswordDialog class which interests me
dialog.setSavedPassword(mSavedPassword);
dialog.setOnPasswordClicked(new PasswordDialog.PasswordClickListener() {
// ...
}
}
#OnClick({R.id.iv_about, R.id.iv_refresh, R.id.bt_auto})
public void onClick(View view) {
switch (view.getId()) {
// ...
case R.id.bt_auto: // When I click on the bt_auto button, the code below is executed
final PasswordDialog dialog = new PasswordDialog(this);
dialog.AutoSetPassword("Moko4321"); // the method I'm trying to launch
// ...
}
}
// ...
}
Now here is the PasswordDialog class which is in another package. I want to modify an element of the passworddialog layout from my Main class :
public class PasswordDialog extends BaseDialog {
#Bind(R.id.et_password)
EditText etPassword; // The element that I want to modify
public PasswordDialog(Context context) {
super(context);
}
// ...
public void AutoSetPassword(String pass) { // My method to modify the EditText
etPassword.setText(pass); // This does not work
// and
((EditText) findViewById(R.id.et_password)).setText(pass); // If I try this instead, it's the same
}
}
Thank you so much for your help :)

AsyncTask: invalidating view does not take effect

Update
My small showcase is stored on Bitbucket
https://bitbucket.org/solvapps/animationtest
I have an Activity with a view in it. Contentview is set to this view.
public class MainActivity extends AppCompatActivity {
private MyView myView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myView = new MyView(this);
setContentView(myView);
startMovie();
}
public void startMovie(){
MovieTask movieTask = new MovieTask(myView, this);
movieTask.doInBackground(null);
}
}
A MovieTask is an Asynctask and refreshes the view periodically.
But invalidate() doesn't refresh the view.
public class MovieTask extends AsyncTask<String, String, String> {
MyView drawingView;
MainActivity mainActivity;
public MovieTask(MyView view, MainActivity mainActivity){
this.mainActivity = mainActivity;
this.drawingView =view;
}
#Override
protected String doInBackground(String... strings) {
for(int i=20;i<100;i++){
drawingView.myBall.goTo(i,i);
publishProgress();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
mainActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
Log.v("DEBUG_DRAW","in onProgressUpdate()");
drawingView.invalidate();
}
});
}
}
Can someone help ?
See how you are launching the AsyncTask:
public void startMovie() {
MovieTask movieTask = new MovieTask(myView, this);
movieTask.doInBackground(null);
}
You are manually calling a method inside some class called MovieTask, thus you are running a code on the same thread. Obviously, that is not your intention, you intended to run the computation code on a background thread.
Correct way to launch AsyncTask is using execute(Params...):
public void startMovie() {
MovieTask movieTask = new MovieTask(myView, this);
movieTask.execute("");
}
Now you will get the desired effect.
P.S.
Please, do not use that code: you do not need to launch a background thread in order to do that kind of stuff. As an alternative consider Animators API.
Declare setBall(int pos) method inside MyBall class:
public class MyView extends View {
...
public void setBall(int pos) {
myBall.setX(pos);
myBall.setY(pos);
invalidate();
}
}
Then change startMovie() to following:
public void startMovie() {
// "ball" means, that Animators API will search for `public setBall(int)` method inside MyView.java and call that method
ObjectAnimator ball = ObjectAnimator.ofInt(myView, "ball", 20, 100);
ball.setDuration(1000);
ball.start();
}
You'll get the same animation without a nasty code.
There is two possible case, first as described in documents:
void invalidate ()
Invalidate the whole view. If the view is visible,
onDraw(android.graphics.Canvas) will be called at some point in the
future.
So try to run your code in onResume, there is a chance that View is not visible yet.
Secondly View#invalidate tells the system to redraw the view as soon as the main UI thread goes idle. That is, calling invalidate schedules your view to be redrawn after all other immediate work has finished.
If you'd like to have your view updated periodically use Handler#postDelay or run it in a separate thread and use View#postInvalidate to update the View and trigger the call to onDraw.

How to manage a DialogFragment with RxJava?

I've been trying to determine if it's possible to create an observable DialogFragment. Essentially I want to be able to:
Create and show a DialogFragment
Get back an rx Observable which can be subscribed to for the result (ok/cancel pressed, String input, background task success/failure, etc.)
Properly handle configuration change
So far the closest thing I've found is ReactiveDialog, which used to be part of RxAndroid, but has been removed from RxAndroid in v1.0.0 as a part of simplifying RxAndroid.
While ReactiveDialog does appear to meet my first two criteria, it does not appear to handle configuration change. There are two issues to consider:
The DialogFragment must maintain its Observable across configuration change so it can notify subscribers of its state.
The subscriber(s) must be able to either hold on to their subscription or re-subscribe after a configuration change (obviously without producing a memory leak).
I'm still fairly new to RxJava, so I'm still trying to wrap my head around how you would manage something like this. It seems like it should be possible, but I feel like it would require a static or singleton Observable manager and possibly retainedInstance DialogFragments.
Anyone have any suggestions or best practices for this?
There are two issues here; one is that you don't want to lose Java Objects during relayout - look into the runtime changes docs about that.
The other issue is that you want to create an Observable that has the action of the dialog, when that action is triggered. For that, have a look at the RxJava docs, the Asynchronous Observer example. You will need to create an Observable.OnSubscribe, and pass that Subscriber to your code that will call the necessary onNext/onError/onCompleted calls.
I would use a ViewModel for the dialog which helps with configuration changes. After a configuration change re-subscribe to the dialog's ViewModel.
1. Components
Screen (Activity/Fragment) - This will display the dialog fragment
DialogFragment - The dialog. Will publish updates about User's actions.
DialogViewModel - holds the User's actions stream
2. Implementation
SimpleActivity
public class SimpleActivity extends AppCompatActivity {
private SimpleDialogViewModel dialogViewModel;
private CompositeDisposable compositeDisposable;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dialogViewModel = ViewModelProviders.of(this).get(SimpleDialogViewModel.class);
compositeDisposable = new CompositeDisposable();
showDialog();
}
#Override
protected void onResume() {
super.onResume();
Disposable disposable =
dialogViewModel
.actionStream()
.subscribe(
result -> {
if (AlertDialog.BUTTON_POSITIVE == result) {
// User clicked yes
}
if (AlertDialog.BUTTON_NEGATIVE == result) {
// User clicked no
}
}
);
compositeDisposable.add(disposable);
}
#Override
protected void onPause() {
super.onPause();
compositeDisposable.clear();
}
private void showDialog() {
SimpleDialogFragment dialogFragment = new SimpleDialogFragment();
dialogFragment.show(getSupportFragmentManager(), SimpleDialogFragment.TAG);
}
}
SimpleDialogFragment
public class SimpleDialogFragment extends DialogFragment {
public static final String TAG = "SimpleDialogFragment";
private SimpleDialogViewModel dialogViewModel;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dialogViewModel = ViewModelProviders.of(getActivity()).get(SimpleDialogViewModel.class);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.dialog_simple_message, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
final View btnYes = view.findViewById(R.id.yes);
final View btnNo = view.findViewById(R.id.no);
btnYes.setOnClickListener(v -> dialogViewModel.onClickYes());
btnNo.setOnClickListener(v -> dialogViewModel.onClickNo());
}
}
SimpleDialogViewModel
public class SimpleDialogViewModel extends ViewModel {
private Subject<Integer> actionSubject;
SimpleDialogViewModel() {
actionSubject = PublishSubject.create();
}
public void onClickYes() {
actionSubject.onNext(AlertDialog.BUTTON_POSITIVE);
}
public void onClickNo() {
actionSubject.onNext(AlertDialog.BUTTON_NEGATIVE);
}
public Observable<Integer> actionStream() {
return actionSubject;
}
}

Prevent onProgressUpdate() data from beeen lost after Fragment detach()

Currently im using a simple Fragment with a attached AsyncTask and setRetainInstance(true) to handle runtime configuration changes and a callback interface to the MainActivity straight from the AsyncTask. (following this example). This works fine so far.
But my Problem is that the data onProgressUpdate passes, once the fragment is detached (when switching to the home-screen for example) is lost. My soloution would be to create buffer variables inside the Fragment which store the lost data from the AsyncTask until the fragment is attached again.
public class MyFragment extends SherlockFragment {
static interface TaskCallbacks {
void onPreExecute();
void onProgressUpdate(MyUpdateBundle p);
void onCancelled();
void onPostExecute();
}
private TaskCallbacks mCallbacks;
private WebFetcherTask mTask;
public List<MyUpdateBundle> updateBuffer;
public MyFragment() {
this.updateBuffer = new ArrayList<MyUpdateBundle>();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallbacks = (TaskCallbacks) activity;
if(updateBuffer.size() > 0)
{
for(MyUpdateBundle update : updateBuffer)
mCallbacks.onProgressUpdate(update);
updateBuffer.clear();
}
}
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
...
private class MyAsyncTask extends AsyncTask<Void, MyUpdateBundle, Void> {
...
#Override
protected void onProgressUpdate(MyUpdateBundle... uB) {
if (mCallbacks != null) {
for(MyUpdateBundle u : uB)
mCallbacks.onProgressUpdate(u);
}
else
{
for(MyUpdateBundle u : uB)
updateBuffer.add(p);
}
}
...
This seems to me the most cleanest soloution, beside saving the data (sinch this is very slow) or using StickyBroadcasts (doesn't seems a clean approach to me). I think that a Service would be a good alternative, but I'm not sure if I would end up in the same problem as here: prevent data from been lost when everthing is unable to recieve. However when I want to re-send the UpdateBundles within the onAtach() methode of the fragment, the buffer is always empty.
I've tryed so far:
volatile statement on the updateBuffer List
Collections.synchronizedList on the updateBuffer List
ensured that the fragment is no create again/twice
put the updateBuffer within the AsyncTask
...
But before I put too much time into this, I would like to know if my approach is even possible and when how.
Thanks in Advance!
Anyway I fixed it. I attached the AsyncTask to the application task and completly removed the Fragment.
It's also a great way to handle any configuration changes or vanished activitys. I don't need to save any data, I just read my buffers and pass them to the callback and have the exact same state as before!

Android: Passing Objects Between Fragments

Before i start, i have look through question such as:
Passing data between fragments: screen overlap
How to pass values between Fragments
as well as Android docs:
http://developer.android.com/training/basics/fragments/communicating.html
as well as this article:
http://manishkpr.webheavens.com/android-passing-data-between-fragments/
Though all the cases mentioned above similar to what i have, it is not entirely identical. I followed a good tutorial here (Some portion of my code is based on this article):
http://www.androidhive.info/2013/10/android-tab-layout-with-swipeable-views-1/
I have the following files:
RegisterActivity.java
NonSwipeableViewPager.java
ScreenSliderAdapter.java
RegisterOneFragment.java
RegisterTwoFragment.java
And the following layouts:
activity_register.xml
fragment_register_one.xml
fragment_register_two.xml
What i am trying to achieve is passing an Serializable object from RegisterFragmentOne to RegisterFragmentTwo.
So far this is what i have done (some codes are omitted):
RegisterActivity.java
public class RegisterActivity extends FragmentActivity
implements RegisterOneFragment.OnEmailRegisteredListener{
public static NonSwipeableViewPager viewPager;
private ScreenSliderAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
// Initilization
mAdapter = new ScreenSliderAdapter(getSupportFragmentManager());
viewPager = (NonSwipeableViewPager) findViewById(R.id.pager);
viewPager.setAdapter(mAdapter);
}
public void onEmailRegistered(int position, Registration regData){
Bundle args = new Bundle();
args.putSerializable("regData", regData);
viewPager.setCurrentItem(position, true);
}
}
ScreenSliderAdapter.java
public class ScreenSliderAdapter extends FragmentPagerAdapter{
public ScreenSliderAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
return new RegisterOneFragment();
case 1:
return new RegisterTwoFragment();
case 2:
return new RegisterThreeFragment();
}
return null;
}
#Override
public int getCount() {
return 3;
}
}
NonSwipeableViewPager.java (extending ViewPager class, and overrides the following)
#Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
// Never allow swiping to switch between pages
return false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// Never allow swiping to switch between pages
return false;
}
RegisterOneFragment.java
public class RegisterOneFragment extends Fragment {
OnEmailRegisteredListener mCallBack;
public interface OnEmailRegisteredListener {
/** Called by RegisterOneFragment when an email is registered */
public void onEmailRegistered(int position, Registration regData);
}
public void onAttach(Activity activity){
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception.
try {
mCallBack = (OnEmailRegisteredListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEmailRegisteredListener");
}
}
... And some to execute some HTTP request via separate thread...
}
What i am trying to accomplish is that when ever a user pressed a button on RegisterOneFragment, a data will be sent to a server (and returns some validation over JSON). If the returned data is valid, the the application should go to the next fragment which is RegistrationTwoFragment.
I am having some confusion as how to pass objects between fragments, since my Fragments is created using an Adapter. And that Adapter is then attached to my Activity.
Can anyone help me with this? Thx
Edit 1:
I tried to make a shortcut (unfortunately does not work) like so:
In RegisterActivity i created:
public Registration regData;
and in RegisterOneFragment:
/* PLACED ON POST EXECUTE */
((RegisterActivity)getActivity()).regData = regData;
Finally called it in RegisterTwoFragment
Registration regData;
regData = ((RegisterActivity) getActivity()).regData;
It throws a nullPointerExceptions
Edit 2
Just to be clear, RegisterActivty contains multiple fragments. And the only way user can navigate between fragment is by clicking a button. The Activity has no Tab bar.
It's easy to share objects via implementing Serializable to your custom Object. I wrote a tutorial about this here.
From Fragment One:
android.support.v4.app.FragmentTransaction ft =
getActivity().getSupportFragmentManager().beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
OfficeCategoryFragment frag = new OfficeCategoryFragment();
Bundle bundles = new Bundle();
Division aDivision = divisionList.get(position);
// ensure your object has not null
if (aDivision != null) {
bundles.putSerializable("aDivision", aDivision);
Log.e("aDivision", "is valid");
} else {
Log.e("aDivision", "is null");
}
frag.setArguments(bundles);
ft.replace(android.R.id.content, frag);
ft.addToBackStack(null);
ft.commit();
In Fragment two:
Bundle bundle = getArguments();
Division division= (Division) bundle.getSerializable("aDivision");
Log.e("division TEST", "" + division.getName());
I would normally have setters or methods similar to this in the containing activity.
So if I understand correctly, you want the user to access RegistrationOneFragment, then when completed, use this data, validate it, and if valid, pass it along to RegistrationTwoFragment and move the user to this Fragment.
Could you simply call validateJson(regData) in your onEmailRegistered method to handle the validation in your activity, if it succeeds, commit a transaction to RegistrationTwoFragment.
Then all you need are getters and setters in your activity or Fragment to say getRegistrationOneData() in the activity or setData(Registration args) in the fragment as your examples show above.
I don't know of any way to pass the args directly into the Fragment.
I found a solution to my question, which i am sure not the correct way to do that...
So in RegisterActivity.java i add + modified the following lines (thx to #sturrockad):
public Registration getRegistrationData(){
return this.regData;
}
public void onEmailRegistered(int position, Registration regData){
this.regData = regData;
viewPager.setCurrentItem(position, true);
}
Then in RegisterTwoFragments.java (or in the Fragment to which i want to receive the Object):
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_register_two, container, false);
regData = ((RegisterActivity) getActivity()).getRegistrationData();
...
I used to set object with Pacelable or Serializable to transfer, but whenever I add other variables to object(model), I have to register it all. It's so inconvenient.
It's super easy to transfer object between activities or fragments.
Android DataCache
put your data object to KimchiDataCache instance in your activity or fragment.
User userItem = new User(1, "KimKevin"); // Sample Model
KimchiDataCache.getInstance().put(userItem);
// add your activity or fragment
Get your data object in your activity of fragment that you added.
public class MainFragment extends Fragment{
private User userItem;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
userItem = KimchiDataCache.getInstance().get(User.class);
}

Categories