android - Proper way to give initial data to fragments?

android - Proper way to give initial data to fragments?

Filibert Nosizwe Author: Filibert Nosizwe Date: 2022-08-19
android - Proper way to give initial data to fragments?

All you need to know about android - Proper way to give initial data to fragments? , in addintion to android - What is the proper way to call MobileAds.initialize()? , android - What is the correct way to share data between different Activities or Fragments? , android - What's the proper way to add/update data in Firestore when offline? , Android Mockito kotlin.UninitializedPropertyAccessException: lateinit property dataManager has not been initialized

  1. android - Proper way to give initial data to fragments?
  2. Question:

    So I've learned that I need an empty constructor in order for my fragments not to crash on reinitialization. My problem is that I use lists of data for my fragments when they are initialized (at least some of them). So what would be a good way to start new fragments with a list of data. Should I in the OnCreate() make a getData method which gets the data from some other source or what would be a proper approach?

    Feeding the bundle with data really wouldn't be a very good approach as I have a lot of data.

    So let's take a case (I understand it tons better that way).

    When a user clicks on a button the fragment is started. What I used to do was creating a new fragment this way:

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        
        fragmentTransaction.replace(R.id.center_container, new DetailFragment(item));
        fragmentTransaction.addToBackStack(DETAIL_TAG);
        
        fragmentTransaction.commit();
    

    Then in my fragment:

    public DetailFragment(EventItem item) {
        mItem = item;
        mPlaces = Database.getContainerPlaces(getActivity()).getValidItems();
    }
    

    I can't give all the data to a bundle, so that wouldn't work. So what should I do?

    A: Should I initialize the fragment with the empty constructor and then from my activity use setters to set the data directly in the fragment? However, won't I be missing data if the user presses home, Android closes the fragment and the user later returns?

    B: Should I initialize the fragment with factory pattern and call setRetainInstance(true), give the fragment a key for identifying the data, and then letting the fragment fetch the data needed in onCreateView from some third source?

    C: Should I just make an empty constructor and then in onCreate() fetch the data needed for the fragment?

    It should be noted that the app is locked in portrait so the issue is primarily with maintaining the objects when Android closes and the user restarts.


    Solution 1:

    So what would be a good way to start new fragments with a list of data.

    Use the factory pattern and the "arguments" Bundle, such as:

    package com.commonsware.empublite;
    
    import android.os.Bundle;
    
    public class SimpleContentFragment extends AbstractContentFragment {
      private static final String KEY_FILE="file";
    
      protected static SimpleContentFragment newInstance(String file) {
        SimpleContentFragment f=new SimpleContentFragment();
    
        Bundle args=new Bundle();
    
        args.putString(KEY_FILE, file);
        f.setArguments(args);
    
        return(f);
      }
    
      @Override
      String getPage() {
        return(getArguments().getString(KEY_FILE));
      }
    }
    

    If you are retaining your fragment instance, you should be able to get away with just using ordinary setters to put stuff in data members. The "arguments" Bundle is retained as part of configuration changes, so for non-retained instances, this is the way to ensure your setup data is retained if the user rotates the screen, etc.

  3. android - What is the proper way to call MobileAds.initialize()?
  4. Question:

    From the reference:

    This method should be called as early as possible, and only once per application launch.

    I thought of calling this method from the Application class of my app the following way:

    public class MyApplication extends Application {
    
        // ...
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            MobileAds.initialize(getApplicationContext(), myAppId);
        }
    }
    

    Is this considered good practice in this case?

    What is the proper way to do this?


    Solution 1:

    Yes this should be fine, and Google actually recommends doing it this way for other packages. For example, in Google's setup guide for Analytics, they recommend initializing the global GoogleAnalytics object inside an Application subclass.

    So yes, this is a proper way to initialize MobileAds.

    Solution 2:

    Yes, your code looks perfect. Some google examples recommend to use Activity.onCreate, but that contradicts to the doc:

    "This method should be called as early as possible, and only once per application launch".

    This thread explains why you should call initialize. Shortly: it speeds up first call AdView.loadAd. At the same time I noticed that it slows down application creation.

    Solution 3:

    Calling MobileAds.initialize() is optional and MobileAds.initialize() executes on Main Thread. So calling it in the onCreate of the Activity will create a lag in the display of your Activity.

    I tested it on OnePlus 6 and execution of MobileAds.initialize() is taking 400-500 ms.

    My Implementation is to call MobileAds.initialize() in a Separate Thread created in the onCreate method of the Application Instance.

    public class CustomApplication extends MultiDexApplication {
        @Override
        public void onCreate() {
            Thread thread = new Thread() {
                @Override
                public void run() {
                    MobileAds.initialize(context, initializationStatus -> {
                        Logger.log("MobileAds init");
                    });
                    //Other heavy task
                }
            };
            thread.start();
        }
    }
    

  5. android - What is the correct way to share data between different Activities or Fragments?
  6. Question:

    I need an app that should have a UI workflow where in a user should be able to browse a particular section of the app which can be either a ListView or a GridView and where he can tap on an item to reveal details of that particular item.Now if the user swipes to the left of the right “i.e. ViewPager” the View pager should change fragment to reveal the next or previous item in the previous List depending on the direction of the User’s swipe,also when the user pressed back on the detailed Item view the existing ViewPager should be closed and the previous ListView or GridView should be shown and the position of the View should be set to the item the user was looking at in the ViewPager.

    To keep things simple and efficient the two views i.e. ListView and the Approach should read and write to the same data structure and they should be in sync such that when the load more data is initiated on one screen and in the meanwhile if the user selects a particular item the next view should update the data automatically after loading completes in the previous screen.

    enter image description here

    Just like Fancy or 9gag

    EDIT: I do not want to maintain a database, I need access to data only till my application's process is alive.


    Solution 1:

    Android provides mapping from String values to various Parcelable types using Bundle.

    For activity:-

    Intent in = new Intent(Sender.this, Receiver.class); 
    in.putString(key, value)
    startActivity(in);
    

    For Fragment use Bundle:-

    Fragment fragment = new Fragment(); 
    Bundle bundle = new Bundle();
    bundle.putInt(key, value);
    fragment.setArguments(bundle);
    

    Edit for your scenario : I think the better option is create ApplicationPool.

    Follow the below steps:- Initiate the ApplicationPool :-

    ApplicationPool pool = ApplicationPool.getInstance();
    

    modify the data on details page and add to pool

    pool.put("key", object);
    

    get the modified data on list page from pool

    Object  object = (Object) pool.get("key");
    

    important notes:- notify the listview or gridview after getting the data

    ApplicationPool class file

    public class ApplicationPool {
    
        private static ApplicationPool instance;
        private HashMap<String, Object> pool;
    
        private ApplicationPool() {
            pool = new HashMap<String, Object>();
        }
    
        public static ApplicationPool getInstance() {
    
            if (instance == null) {
                instance = new ApplicationPool();
    
            }
    
            return instance;
        }
    
        public void clearCollectionPool() {
            pool.clear();
        }
    
        public void put(String key, Object value) {
            pool.put(key, value);
        }
    
        public Object get(String key) {
            return pool.get(key);
        }
    
        public void removeObject(String key) {
    
            if ((pool.get(key)) != null)
                pool.remove(key);
    
        }
    }
    

  7. android - What's the proper way to add/update data in Firestore when offline?
  8. Question:

    When my app is offline and I am adding or updating a document the memory increases. Also, showing lists of documents take longer to load. If I run the same code when the device is online the memory stays consistent as well as the speed of the activities that have lists of documents.

    I'm currently doing saves like the following:

    collectionRef.document(id).set(obj, SetOptions.merge());
    

    Or for batching a couple of records:

    batch.set(docRef1, obj1);
    batch.set(docRef2, obj2);
    batch.commit();
    

    I had listeners for onComplete but in the accepted answer for this question, it seems to indicate that listeners are unnecessary in most situations and that you can't wait for it to complete anyway when you're offline.

    In another question they indicate in code that a "Snapshot" is required to properly do online and offline saving: Offline issue with Firestore vs Firebase. But I can't find anywhere else indicating if that will make a difference. I think of Snapshots as being something you attach to a document or query when you want to be notified of changes to it and attaching a listener like that will result in a memory leak if it isn't removed.

    Another part of all of this is how this slowness might affect data integrity. When I watch in the profiler in Android Studio I see that the FirestoreWorker can get to a point where it is constantly working even if I don't do anything in the app. I'm not just talking a few seconds, more like a minute. There isn't any ordering guarantee of writes when it is offline that I can find. Trying to stop and restart the app doesn't seem to have any effect on the slowness (although it will reset the memory).

    So all of this leads to the question: what is the proper way to add/update data in Firestore when offline so that the app's memory doesn't grow unbounded and slow down?


    Solution 1:

    Cloud Firestore uses SQLite for its persistence mechanism. So for intermittent periods of offline activity, you shouldn't have problems with performance or durability.

    However, if you intend to use a Firestore database for very long periods of time, there are some things you should be aware of. Cloud Firestore was not built as an offline database, is an online database that continues to work when you're offline for short or longer periods of time. When offline, pending writes that have not yet been synced to the server are held in a queue. If you do too many write operations without going online to sync them, that queue will grow fast and it will not slow down only the write operations it will also slow down your read operations.

    So I suggest use this database for its online capabilities. As one of the Firebase engineers said and I quote, "It is impossible to build a slow query in Firestore". So, the performance comes from the new indexing capabilities on the backend and these optimizations don't exist when you're offline.

    One more thing, if you have many offline clients who are trying to write to the same document, only the last one will be actually be written to servers when the state is changed.

    So, to answer your question, there is no proper way to add/update data in Firestore when offline, to have a less memory usage. Just go online and that's it!

  9. Android Mockito kotlin.UninitializedPropertyAccessException: lateinit property dataManager has not been initialized
  10. Question:

    I constantly get kotlin.UninitializedPropertyAccessException: lateinit property xxx has not been initialized in my Mockito test. But the app works just fine. Note: I don't want to inject presenter into activity. Thanks in advance!

    Here's my Activity:

    class CreateAccountActivity : AppCompatActivity(), CreateAccountView {
    
    private var presenter: CreateAccountPresenter? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_create_account)
        presenter = CreateAccountPresenter()
        ((application) as CariumApp).getDaggerComponent().inject(presenter!!)
    
        presenter?.attachView(this)
    }
    

    And here's my Presenter:

    class CreateAccountPresenter {
    private var view: CreateAccountView? = null
    
    @Inject
    lateinit var dataManager: DataManager
    
    fun attachView(view: CreateAccountView) {
        this.view = view
    
        dataManager.getServiceDocuments(true, object : GetServiceDocumentsListener { 
           // ...
        })
    }
    

    Here's my DataManager:

    interface DataManager {
        fun getServiceDocuments(latest: Boolean, listener: GetServiceDocumentsListener)
    }
    

    and AppDataManager:

    Singleton
    class AppDataManager @Inject constructor(context: Context) : DataManager {
    // ...
    }
    

    and finally my test that's failing:

    class CreateAccountPresenterTest {
    
    val mockDataManager: DataManager = mock()
    
    val mockCreateAccountView: CreateAccountView = mock()
    
    private val createAccountPresenter = CreateAccountPresenter()
    
    @Test
    fun getServiceDocuments() {
        doAnswer {
            val args = it.arguments
            (args[1] as GetServiceDocumentsListener).onError()
            null
        }.`when`(mockDataManager).getServiceDocuments(Mockito.anyBoolean(), anyOrNull())
    
        createAccountPresenter.attachView(mockCreateAccountView)
    
        verify(mockCreateAccountView).hideLoadingDialog()
    }
    }
    

    gradle file:

    testImplementation 'junit:junit:4.12'
    testImplementation 'org.mockito:mockito-core:2.22.0'
    testImplementation "org.mockito:mockito-inline:2.22.0"
    testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0-RC1"
    implementation 'com.google.dagger:dagger:2.16'
    kapt 'com.google.dagger:dagger-compiler:2.16'
    

    My module class:

    @Module
    open class MyModule(private var context: Context) {
    
    @Provides
    open fun provideContext(): Context {
        return context
    }
    
    @Provides
    @Singleton
    internal fun provideDataManager(appDataManager: AppDataManager): DataManager {
        return appDataManager
    }
    }
    

    Actual error is kotlin.UninitializedPropertyAccessException: lateinit property dataManager has not been initialized


    Solution 1:

    You are not assigning your mock to the field. Assign it in your test method. Before calling attachView()

    createAccountPresenter.dataManager = mockDataManager
    

    Solution 2:

    Where do you have DataManager @Provides method? Dagger recognizes @Inject constructor inside AppDataManager but cannot recognize it as interface. Create Module for Dagger that is abstract and uses @Binds

    https://proandroiddev.com/dagger-2-annotations-binds-contributesandroidinjector-a09e6a57758f