All Ways for Passing Data

ahmed shaaban
7 min readSep 3, 2020

--

1- passing data between two activities
2- Passing data between two fragments
3- Passing data between Activity and Fragment

I was asked this Question in interview 😅😅

1️⃣ Passing data between two activities

  • Intent
    is an abstract concept of work or functionality that can be performed by your app sometime in the future (action +data)
val i:Intent =Intent(getApplicationContext(), NewActivity.class)
i.putExtra("new_variable_name","value")
startActivity(i);
  • DataHolder and Hashmap
    You can use a singleton class to store objects and retrieve them in other activity by getting instance of that singleton class.

if you have a lot of arguments and/or complex objects you wish to move from one place to the other. Simply create a single ‘holder’ object containing getter/setters for the arguments and then pass it along. Much simpler than having to pass them separately and somehow serialize the complex objects.

object DataHolder {
private val data: MutableMap<String, Any> = HashMap()

var id: Int
get() {
val resault = data["ID"]
return resault as Int
}

set(value) {
data["ID"] = value
}
}

Note Don’t use weekReference with data or data will be lost if android need space Using WeakReference<Object> will clear the data variable in DataHolder class when reach to setContentView(R.layout.Some_Activity) line in second activity.

  • Event bus

Example passing an object from one Activity to another:

Add your object on the EventBus in ActivityA:

EventBus.getDefault().postSticky(anyObject);
startActivity(new Intent(getActivity(), ActivityB.class));

Note we can use registerSticky and postSticky instead of register and post.

in Activity B:

AnyObject anyObject = EventBus.getDefault().getStickyEvent(AnyObject.class);
  • Application
    use when share global variables between activities
    Case:- the session ID available to every activity where you want to allow the user to signout. Alternatively, you could store it in the Application object, but then you’d have to manage the state of the session (check if it’s valid before using, etc).
public class MyApplication : Application {      private var globalVariable:Int =1      fun getGlobalVariable():Int {
return globalVariable
}
public fun setGlobalVariable(globalVariable:Int) {
this.globalVariable = globalVariable;
}
@Override
public fun onCreate() {
//reinitialize variable
}
}

problem here :- (data stored in the application class can be lost)

imagine you leave app using home button and Android silently kills the app to reclaim some memory when you reopen app Android will create new instance for MyApplication which in turn make globalVariable = null and if you don’t make checking will crash your app

it is also true for any singleton or public static field that you might have in your app.

it’s perfectly fine to init a singleton inside oncreate in MyApplication that sets up the retrofit service, which is what I’ll continue to do until someone offers a better way.

if we persist application state in the application class -using viewmodel factory- , a good design will call for all activities to take action depending on that state value [including null ] — because that is what the purpose of state !

this should only be a cached copy of what you store in shared preferences, since the application object can be killed at some point.

  • RxJava
    Create the bus:
    Note
    Use BehaviorSubject as it emits the most recently emitted item and all the subsequent items of the source Observable when a observer subscribe to it.
public final class RxBus {
private static final BehaviorSubject<Object> behaviorSubject
= BehaviorSubject.create();
public static BehaviorSubject<Object> getSubject() {
return behaviorSubject;
}
}

the sender activity

//the data to be based
MyData data =getMyData();
RxBus.getSubject().onNext(data) ;
startActivity(new Intent(MainActivity.this, AnotherAct.class));

the receiver activity

disposable = RxBus.getSubject().
subscribeWith(new DisposableObserver<Object>() {
@Override
public void onNext(Object o) {
if (o instanceof MyData) {
Log.d("tag", (MyData)o.getData();
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
});

unSubscribe to avoid memory leacks:

@Override
protected void onDestroy() {
super.onDestroy();
disposable.dispose();
}
  • ShredPrefrence and other way to persist data (use SharedPreferences to store the data in one activity and read it in another activity).
var prefs= getSharedPreferences("myPrefsKey", Context.MODE_PRIVATE)
.edit()
prefs.putInt("key2", 200)
prefs.commit()

when we use Application class => this should only be a cached copy of what you store in shared preferences, since the application object can be killed at some point.

Problem:-
SharedPreferences uses <Key,Value> pair concept. We need to check for !null or empty string every time when we need to read the value, which results in a lot of duplicate code

  • Wagon
    Link
  • LiveData
    The fact that LiveData objects are lifecycle-aware means that you can share them between multiple activities, fragments, and services.

this observer is bound to the Lifecycle object associated with the owner, meaning:
- If the Lifecycle object is not in an active state, then the observer isn’t called even if the value changes.
- After the Lifecycle object is destroyed, the observer is automatically removed

  • LocalBroadcastManager but has been deprecated
    LocalBroadcastManager is an application-wide event bus and embraces layer violations in your app; any component may listen to events from any other component. It inherits unnecessary use-case limitations of system BroadcastManager; developers have to use Intent even though objects live in only one process and never leave it. For this same reason, it doesn’t follow feature-wise BroadcastManager .

in some case i use it i made it weak reference because i might forget to unregister it but anyway stop using it one day i think google developer will stop over Engineering thinking i hope 😂😂

2️⃣ Passing data between two fragments

  • Broadcast Receiver
    The best way to handle asynchronously calls from fragments and return them data is to implement BroadcastReceivers on both sides. You should anyway make it asynchronous if you’re working with non-specific number of fragments.
  • Event Bus
    So, you can post an object in Fragment A, and use getStickyEvent in fragment B to retrieve it. as i used it in Activity above
  • BaseFragment
    I create a BaseFragmetnt as abstract and add a abstract method onDataPassed() and All my fragments extends this BaseFragment. When passing data is needed,just find the fragment and call onDataPassed is OK. May Help.
  • Callback(Inter Fragment Design)
    1- create interface as event carrier
    2- make activity implement interface
    3- let fragment a use interface to send message
    4- use callback in activity to trigger change in fragmentB
Image source: https://stackoverflow.com/a/24083101/1841194
  • DataHolde
    as i made in Activity
  • SharedViewModel
    -
    Using SharedViewModel, we can communicate between fragments. If we consider two fragments, both the fragments can access the ViewModel through their activity.
    - One fragment updates the data within the ViewModel which is shared between both the fragments and another fragment observes the changes on that data.
class SharedViewModel : ViewModel() {
val obj= MutableLiveData<MyObject>()
fun sendMessage(obj: MyObject) {
obj.value = obj
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?){
super.onViewCreated(view, savedInstanceState)

val model= ViewModelProvider(requireActivity())
.get(SharedViewModel::class.java)

model.myObject.observe(viewLifecycleOwner, Observer {
// work
})
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?){
super.onViewCreated(view, savedInstanceState)
model = ViewModelProvider(requireActivity())
.get(SharedViewModel::class.java)
button.setOnClickListener { model.sendData(MyObject()) }
}
  • Repository Pattern (LiveData or reactive streams(as i made it above).)
    You can extend a LiveData object using the singleton pattern to wrap system services so that they can be shared in your app. The LiveData object connects to the system service once, and then any observer that needs the resource can just watch the LiveData object.
class MyFragment : Fragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
val newValue:LiveData<Int> = ...
newValue.observe(this, value -> {
// Update the UI.
})
}
}

3️⃣ Passing data between Activity and Fragment

  • (From Activity to Fragment) in fragment you can call getActivity().
    This will give you access to the activity that created the fragment. From there you can obviously call any sort of accessor methods that are in the activity.
    Note(1) you will need to cast your getActivity()
    Note(2) is good if you statically link your fragment with the one activity descendant but If you want to use fragment from more than one activity
public class MyActivity extends Activity {

private String myString = "hello";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
...
}

public String getMyData() {
return myString;
}
}
public class MyFragment extends Fragment {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

MyActivity activity = (MyActivity) getActivity();
String myDataFromActivity = activity.getMyData();
return view;
}
}
  • (From Activity to Fragment) using Arguments
        var bundle:Bundle = Bundle()
bundle.putString("message", "Alo Elena!");
FragmentClass fragInfo = new FragmentClass();
fragInfo.setArguments(bundle);
transaction.replace(R.id.fragment_single, fragInfo);
transaction.commit();
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
String myValue = this.getArguments().getString("message")
}
  • (From Fragment to Activity )using Callback
    you could create an interface and force the activity to implement it and then call a method from within your fragment to pass the data.
    Note(1) Use the onAttach method to check whether the activity implements the interface.
class MyFragment : Fragment() {
internal var callback: OnHeadlineSelectedListener? = null
lateinit var callback2: OnHeadlineSelectedListener
// First choise
fun setListener(callback: OnHeadlineSelectedListener) {
this.callback = callback
}
// Second choise
override fun onAttach(context: Context) {
super.onAttach(context)
callback2= context as OnHeadlineSelectedListener
}
interface OnHeadlineSelectedListener {
fun onArticleSelected(position: Int)
}
}
class MainActivity:Activity(), MyFragment.OnHeadlineSelectedListener {
fun onAttachFragment(fragment: Fragment) {
if (fragment is MyFragment) {
fragment.setSelectedListener(this)
}
}
}
  • using SharedViewModel as i made top
  • using livedata or reactive stream as i mentioned above

--

--