Application architecture: ViewPager2, Fragments and MutableLiveData - java

Good evening,
I am new at Android App development. I have a project where I receive data from multiple sensors which are connected an instrument.
At this time, I can select any sensor and stream its data using AudioTrack. The streaming is from a service and this service is bound to the MainActivity instance. A single sensor data is streamed at the time. This is working as expected.
Now, I want to plot (using MPAndroidChart) the data for each sensor in its own chart. In the activity_main layout, I added a ViewPager2 element, and Fragments are instantiated by the PagerAdapter::CreateFragment(int position) method.
My class model is as follow (based on a ViewPager2 example):
public class MainActivity extends AppCompatActivity
public class PagerAdapter extends FragmentStateAdapter
public class ChannelFragment extends Fragment
public class SensorViewModel extends ViewModel
I tested the visualisation of the plots in the Fragments using mock data (and a different colour background). The mock data was generated in ChannelFragment::onCreateView() method. This also works, though the mock data is the same for all fragments/charts, but different background colour for each fragment.
In a live test, I also proved to myself that I process changing the visible fragment correctly. I associate each fragment to a specific sensor and the streaming data changes as the visible fragment changes. This is done from MainActivity using ViewPager2.OnPageChangeCallback onPageSelected(position)
I am able to set the sensor data to the SensorViewModel and trigger a notification when calling postValue() from SensorViewModel.
The SensorViewModel contains the following declaration
private MutableLiveData<float[][]> sensorLiveData = new MutableLiveData<>();
and is created in MainActivity::onCreate(Bundle savedInstanceState)
// Create a ViewModel to hold the audio (sensor) data. This view model is used to communicate
// with the UI, graphically display the audio data for example
sensorViewModel = new ViewModelProvider(this).get(SensorViewModel.class);
The code for the observer is defined as a ChannelFragment method and listed as
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
sensorViewModel = new ViewModelProvider(requireActivity()).get(SensorViewModel.class);
sensorViewModel.getSampleData().observe(requireActivity(), new Observer<float[][]>() {
#Override
public void onChanged(#Nullable float[][] sampleData)
{
// Update the sample data graphical representation
Log.d(TAG, String.format("ChannelFragment; onChanged() sampleData length : %d, size: %d",
sampleData.length, sampleData[0].length));
addEntry(sampleData);
}
});
And the log statement reports
ChannelFragment: ChannelFragment; onChanged() sampleData length : 8, size: 170
I am stuck as to update the correct chart (correct ChannelFragment) with its corresponding sensor data.
How to a plot sampleData[0][] in Fragment at position 0, sampleData1 in Fragment at position 1, and so forth?
Calls to ChannelFragment.getId() always returns 0, though I am able to access other attributes such as title when different fragments become visible.
I also modified the SensorVIewModel as suggested by Tiago Redaelli. The new declaration:
private HashMap<Integer, MutableLiveData<ArrayList<Float>>> sensorLiveData = new HashMap<>();
where the key identifies the sensor/fragment the MutableLiveData belongs to. However, the example does not explain how to set (inject?) the ViewModel (and MutableLiveData value) to the ChannelFragment. In the example, the method setViewModel() is implemented but call is not shown.
Using this new ViewModel definition, how do I associate each HashMap values with their corresponding Fragment (who is calling setViewModel() ? What I am missing?
Thanks in advance
Daniel

Just a quick update. I was trying to create a complicated solution to my issue, including looking at ViewModel factory for example.
In my solution, I simply call setViewModel() (mentioned above) using the position/index value passed as argument when creating an instance of ChannelFragment.
This appears to be working.

Related

Passing data between Parent fragment and Child fragment

I'm struggling with a communication between 2 or more fragments
My application architecture looks like this:
MainActivity (Menu Drawer)
----> MealsFragment
----> ProfileFragment
----> StatsFragment
----> SportFragment
----> ContactUsFragment
(By clicking on menu drawer main_container is replaced with selected Fragment)
Earlier, I had single activities instead of fragments but I have read that I need to convert them into Fragments to correctly implement menu drawer (so I did it).
The problem is each of the activities had its own child-activities which were communicating with parents with using onActivityResult. Now I don't know how to do it with fragments.
The scenario is like this:
Open MealsFragment from MainActivity (it works)
Open AddMealFragment from MealsFragment (with data from MealsFragment), fill the form and then return all information provided there by user to MealsFragment
Use received data for further actions
I have already seen posts recommending using settargetFragment() and getTargetFragment() but I don't know how to do it and - what is more important - I don't know how to receive the data afterwards
I hope you are aware of how to do Fragment transactions, i.e. using Fragment Manager class to add or remove fragments from the backstack. If you don't know then it's a good idea to learn that first.
Now that you know how to add or remove fragments, passing data among them is a simple thing of all of that in fragment's Bundle that you can access in the receiver fragment. Here's an example:
class ConversationFragment : Fragment() {
companion object {
const val JOB_REQUEST_ID = "jobRequestId"
#JvmStatic
fun newInstance(jobRequestId: String) =
ConversationFragment().apply {
arguments = Bundle().apply { putString(JOB_REQUEST_ID, jobRequestId) }
}
}
}
In order to create ConversationFragment, I expose a newInstance method that the FragmentManager or any other entity can use to create one. However, they would need to pass a JOB_REQUEST_ID in order to create one. I simply put this id in a Bundle and pass it on to the fragment as its argument. On the receiver side(fragment), you can get a handle to this bundle and retrive the value:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
jobRequestId = it.getString(JOB_REQUEST_ID)
}
}

Send Object created from MainActivity to Fragments TabbedView [duplicate]

This question is mostly to solicit opinions on the best way to handle my app. I have three fragments being handled by one activity. Fragment A has one clickable element the photo and Fragment B has 4 clickable elements the buttons. The other fragment just displays details when the photo is clicked. I am using ActionBarSherlock.
The forward and back buttons need to change the photo to the next or previous poses, respectively. I could keep the photo and the buttons in the same fragment, but wanted to keep them separate in case I wanted to rearrange them in a tablet.
I need some advice - should I combine Fragments A and B? If not, I will need to figure out how to implement an interface for 3 clickable items.
I considered using Roboguice, but I am already extending using SherlockFragmentActivity so that's a no go. I saw mention of Otto, but I didn't see good tutorials on how to include in a project. What do you think best design practice should be?
I also need help figuring out how to communicate between a fragment and an activity. I'd like to keep some data "global" in the application, like the pose id. Is there some example code I can see besides the stock android developer's information? That is not all that helpful.
BTW, I'm already storing all the information about each pose in a SQLite database. That's the easy part.
The easiest way to communicate between your activity and fragments is using interfaces. The idea is basically to define an interface inside a given fragment A and let the activity implement that interface.
Once it has implemented that interface, you could do anything you want in the method it overrides.
The other important part of the interface is that you have to call the abstract method from your fragment and remember to cast it to your activity. It should catch a ClassCastException if not done correctly.
There is a good tutorial on Simple Developer Blog on how to do exactly this kind of thing.
I hope this was helpful to you!
The suggested method for communicating between fragments is to use callbacks\listeners that are managed by your main Activity.
I think the code on this page is pretty clear:
http://developer.android.com/training/basics/fragments/communicating.html
You can also reference the IO 2012 Schedule app, which is designed to be a de-facto reference app. It can be found here:
http://code.google.com/p/iosched/
Also, here is a SO question with good info:
How to pass data between fragments
It is implemented by a Callback interface:
First of all, we have to make an interface:
public interface UpdateFrag {
void updatefrag();
}
In the Activity do the following code:
UpdateFrag updatfrag ;
public void updateApi(UpdateFrag listener) {
updatfrag = listener;
}
from the event from where the callback has to fire in the Activity:
updatfrag.updatefrag();
In the Fragment implement the interface in CreateView do the
following code:
((Home)getActivity()).updateApi(new UpdateFrag() {
#Override
public void updatefrag() {
.....your stuff......
}
});
To communicate between an Activity and Fragments, there are several options, but after lots of reading and many experiences, I found out that it could be resumed this way:
Activity wants to communicate with child Fragment => Simply write public methods in your Fragment class, and let the Activity call them
Fragment wants to communicate with the parent Activity => This requires a bit more of work, as the official Android link https://developer.android.com/training/basics/fragments/communicating suggests, it would be a great idea to define an interface that will be implemented by the Activity, and which will establish a contract for any Activity that wants to communicate with that Fragment. For example, if you have FragmentA, which wants to communicate with any activity that includes it, then define the FragmentAInterface which will define what method can the FragmentA call for the activities that decide to use it.
A Fragment wants to communicate with other Fragment => This is the case where you get the most 'complicated' situation. Since you could potentially need to pass data from FragmentA to FragmentB and viceversa, that could lead us to defining 2 interfaces, FragmentAInterface which will be implemented by FragmentB and FragmentAInterface which will be implemented by FragmentA. That will start making things messy. And imagine if you have a few more Fragments on place, and even the parent activity wants to communicate with them. Well, this case is a perfect moment to establish a shared ViewModel for the activity and it's fragments. More info here https://developer.android.com/topic/libraries/architecture/viewmodel . Basically, you need to define a SharedViewModel class, that has all the data you want to share between the activity and the fragments that will be in need of communicating data among them.
The ViewModel case, makes things pretty simpler at the end, since you don't have to add extra logic that makes things dirty in the code and messy. Plus it will allow you to separate the gathering (through calls to an SQLite Database or an API) of data from the Controller (activities and fragments).
I made a annotation library that can do the cast for you. check this out.
https://github.com/zeroarst/callbackfragment/
#CallbackFragment
public class MyFragment extends Fragment {
#Callback
interface FragmentCallback {
void onClickButton(MyFragment fragment);
}
private FragmentCallback mCallback;
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt1
mCallback.onClickButton(this);
break;
case R.id.bt2
// Because we give mandatory = false so this might be null if not implemented by the host.
if (mCallbackNotForce != null)
mCallbackNotForce.onClickButton(this);
break;
}
}
}
It then generates a subclass of your fragment. And just add it to FragmentManager.
public class MainActivity extends AppCompatActivity implements MyFragment.FragmentCallback {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction()
.add(R.id.lo_fragm_container, MyFragmentCallbackable.create(), "MY_FRAGM")
.commit();
}
Toast mToast;
#Override
public void onClickButton(MyFragment fragment) {
if (mToast != null)
mToast.cancel();
mToast = Toast.makeText(this, "Callback from " + fragment.getTag(), Toast.LENGTH_SHORT);
mToast.show();
}
}
Google Recommended Method
If you take a look at this page you can see that Google suggests you use the ViewModel to share data between Fragment and Activity.
Add this dependency:
implementation "androidx.activity:activity-ktx:$activity_version"
First, define the ViewModel you are going to use to pass data.
class ItemViewModel : ViewModel() {
private val mutableSelectedItem = MutableLiveData<Item>()
val selectedItem: LiveData<Item> get() = mutableSelectedItem
fun selectItem(item: Item) {
mutableSelectedItem.value = item
}
}
Second, instantiate the ViewModel inside the Activity.
class MainActivity : AppCompatActivity() {
// Using the viewModels() Kotlin property delegate from the activity-ktx
// artifact to retrieve the ViewModel in the activity scope
private val viewModel: ItemViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.selectedItem.observe(this, Observer { item ->
// Perform an action with the latest item data
})
}
}
Third, instantiate the ViewModel inside the Fragment.
class ListFragment : Fragment() {
// Using the activityViewModels() Kotlin property delegate from the
// fragment-ktx artifact to retrieve the ViewModel in the activity scope
private val viewModel: ItemViewModel by activityViewModels()
// Called when the item is clicked
fun onItemClicked(item: Item) {
// Set a new item
viewModel.selectItem(item)
}
}
You can now edit this code creating new observers or settings methods.
There are severals ways to communicate between activities, fragments, services etc. The obvious one is to communicate using interfaces. However, it is not a productive way to communicate. You have to implement the listeners etc.
My suggestion is to use an event bus. Event bus is a publish/subscribe pattern implementation.
You can subscribe to events in your activity and then you can post that events in your fragments etc.
Here on my blog post you can find more detail about this pattern and also an example project to show the usage.
I'm not sure I really understood what you want to do, but the suggested way to communicate between fragments is to use callbacks with the Activity, never directly between fragments. See here http://developer.android.com/training/basics/fragments/communicating.html
You can create declare a public interface with a function declaration in the fragment and implement the interface in the activity. Then you can call the function from the fragment.
I am using Intents to communicate actions back to the main activity. The main activity is listening to these by overriding onNewIntent(Intent intent). The main activity translates these actions to the corresponding fragments for example.
So you can do something like this:
public class MainActivity extends Activity {
public static final String INTENT_ACTION_SHOW_FOO = "show_foo";
public static final String INTENT_ACTION_SHOW_BAR = "show_bar";
#Override
protected void onNewIntent(Intent intent) {
routeIntent(intent);
}
private void routeIntent(Intent intent) {
String action = intent.getAction();
if (action != null) {
switch (action) {
case INTENT_ACTION_SHOW_FOO:
// for example show the corresponding fragment
loadFragment(FooFragment);
break;
case INTENT_ACTION_SHOW_BAR:
loadFragment(BarFragment);
break;
}
}
}
Then inside any fragment to show the foo fragment:
Intent intent = new Intent(context, MainActivity.class);
intent.setAction(INTENT_ACTION_SHOW_FOO);
// Prevent activity to be re-instantiated if it is already running.
// Instead, the onNewEvent() is triggered
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(intent);
There is the latest techniques to communicate fragment to activity without any interface follow the steps
Step 1- Add the dependency in gradle
implementation 'androidx.fragment:fragment:1.3.0-rc01'

Android: creating a Custom view as Marker in Google Maps API

Strangely enough, I haven't seen a proper answer anywhere else on here. It might not be possible, but I'm still taking a shot.
In the latest Google Maps API version for Android, I want to create a custom View that would be placed over the marker like so once you click on it :
Map Preview
Of course, I want to be able to interact with its intern components, i.e. the listview.
The menu would still show up and remain attached to the marker even if you move around on the map.
From what I've seen you can only render a Bitmap on the map. Is there really no workaround ?
Thanks in advance
Use Info window to display custom layout on google maps.
This is the sample code from the document.
public class MarkerDemoActivity extends AppCompatActivity implements
OnInfoWindowClickListener,
OnMapReadyCallback {
private GoogleMap mMap;
#Override
public void onMapReady(GoogleMap map) {
mMap = map;
// Add markers to the map and do other map setup.
...
// Set a listener for info window events.
mMap.setOnInfoWindowClickListener(this);
}
#Override
public void onInfoWindowClick(Marker marker) {
Toast.makeText(this, "Info window clicked",
Toast.LENGTH_SHORT).show();
}
}
However, Info window is not a live View, rather the view is rendered as an
image onto the map. As a result, any listeners you set on the view are
disregarded and you cannot distinguish between click events on various
parts of the view. You are advised not to place interactive components
— such as buttons, checkboxes, or text inputs — within your custom
info window.

Android Espresso functional tests with fragments

I have three activities in my app
A login activity
A main activity
A detail activity
I want to use espresso to test a sequence of events: click the login button on the login activity, which opens the main activity, and then click a list item in main activity, which opens detail activity, and then click another button in the detail activity. I started by creating this simple test, to get a reference to the listview:
public class LoginActivityTest extends ActivityInstrumentationTestCase2<LoginActivity> {
public LoginActivityTest() {
super(LoginActivity.class);
}
#Override
public void setUp() throws Exception {
super.setUp();
getActivity();
}
public void testSequence() throws Exception {
// Login
onView(withId(R.id.button_log_in)).perform(click());
// Check if MainActivity is loaded
onView(withId(R.id.container)).check(matches(isDisplayed()));
// Check if Fragment is loaded
onView(withId(R.id.list)).check(matches(isDisplayed()));
}
}
On the mainActivity onCreate() method I load a fragment like this:
getFragmentManager().beginTransaction()
.add(R.id.container, mListFragment)
.commit();
The ListFragment fragment has a list (R.id.list), but still the test fails with a NoMatchingViewException:
android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with id: com.tests.android.development:id/list
What am I doing wrong?
A note from the documentation for onView:
Note: the view has to be part of the view hierarchy. This may not be
the case if it is rendered as part of an AdapterView (e.g. ListView).
If this is the case, use Espresso.onData to load the view first.
To use onData to load the view, you need to check for instances of whatever your adapter is in the ListView. In other words, if your listview uses a Cursor adapter, you can try this:
onData(allOf(is(instanceOf(Cursor.class)))).check(matches(isDisplayed()));
It is important to note that the above will only pass if your listview contains at least one item. It is a good idea to have one test where an item exists, and one test where an item does not.
For more information on how to check for data that does exist, see here.
For more information on how to check for data that does not exist in an adapter, see here.
In the current version (Espresso 2.2.2) this exception is always appended with a View Hierarchy: clause that lists all the views available to match. Stroll through that and check if you can find your list.
As an alternative: check out android-sdk\tools\uiautomatorviewer.bat (or .sh) which takes a snapshot from the current screen and hierarchy. Put a breakpoint on your list matching line and check with the viewer if the list is there. If you find the list, there may be a timing issue in the test. Maybe it didn't wait enough, check out more about IdlingResources.

Losing 'MediaPlayer' (& other Variables) when Device is Rotated

I'm creating a music player for Android and it's mostly working. The problem is that when I turn the device horizontally I lose all the variables from the Activity (which makes sense because it is destroyed and re-created).
I've tried using bundles to store the state of the player with onSaveInstanceState & onRestoreInstanceState but I still can't access the media player. Is there a way to pass objects like the MediaPlayer in bundles? Should I be using a database instead?
Thanks
You should use a Service to Provides "background" audio playback capabilities, allowing the
user to switch between activities or Rotate device without stopping playback.
Check out android_packages_apps_Music which is opensource by CM on github , It use MediaPlaybackService extends Service to do this , checkout MediaPlaybackService.java
For objects you couldn't pass via a bundle, I would suggest you to use the simple SharedPreference to store objects.
Here you have a simple implementation:
public class Data {
private SharedPreferences preferences;
private int test;
public Data (Context context)
{
preferences = context.getSharedPreferences("Data", 0);
test = preferences.getInt("test", 0);
}
public int getTest()
{
return test;
}
public void setTest(int input)
{
this.test = input;
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("Test", input);
editor.commit();
}
}
You have just to initialize the variable in the onCreate():
Data mydata = new Data(this);
And you can use set/get with mydata to store/retrieve your persistent data.
Edit: It is maybe not suitable for MediaPlayer objects, but for other classical types (int, string, boolean...).
Both of the methods below would allow you to keep your mediaplayer object through the rotation, but neither use bundles.
You could persist your media player by using onRetainNonConfigurationInstance() to save the variable and getLastNonConfigurationInstance() to retrieve it after the rotation, but this method isn't necessarily the best as it is not always called
-See this SO post for more info https://stackoverflow.com/a/3916068/655822
Or you could persist your media player by extending your application class and storing it in there
below info copied from the linked SO answer for the purpose of making this answer quicker to read
You can pass data around in a Global Singleton if it is going to be used a lot.
public class YourApplication extends Application
{
public SomeDataClass data = new SomeDataClass();
}
Then call it in any activity by:
YourApplication appState = ((YourApplication)this.getApplication());
appState.data.UseAGetterOrSetterHere(); // Do whatever you need to with the data here.
-See this SO post for more info on that https://stackoverflow.com/a/4208947/655822
Another way would be to :
In your AndroidManifest.xml, find your entry for your activity and add the following attribute and value:
android:configChanges="orientation|screenSize"
This will stop your activity from being destroyed and recreated on orientation.

Categories

Resources