{"id":10302,"date":"2023-08-05T17:55:00","date_gmt":"2023-08-05T21:55:00","guid":{"rendered":"https:\/\/www.answertopia.com\/?p=10302"},"modified":"2023-12-14T14:31:02","modified_gmt":"2023-12-14T19:31:02","slug":"an-android-studio-java-livedata-tutorial","status":"publish","type":"post","link":"https:\/\/www.answertopia.com\/android-studio\/an-android-studio-java-livedata-tutorial\/","title":{"rendered":"An Android Studio Java LiveData Tutorial"},"content":{"rendered":"\n<p>The <a href=\"https:\/\/www.answertopia.com\/android-studio\/an-android-studio-java-viewmodel-tutorial\/\" data-type=\"post\" data-id=\"10295\">previous chapter<\/a> began building an app to conform to the recommended Jetpack architecture guidelines. These initial steps involved implementing the data model for the app user interface within a ViewModel instance.<\/p>\n\n\n\n<p>This chapter will further enhance the app design using the LiveData architecture component. Once LiveData support has been added to the project in this chapter, the next chapters (starting with <em>\u201cAn Overview of Android Jetpack Data Binding\u201d<\/em>) will use the Jetpack Data Binding library to eliminate even more code from the project.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">LiveData &#8211; A Recap<\/h2>\n\n\n\n<p>LiveData was previously introduced in the <a href=\"https:\/\/www.answertopia.com\/android-studio\/modern-android-app-architecture-with-jetpack\/\" data-type=\"post\" data-id=\"9247\">Modern Android App Architecture with Jetpack<\/a> chapter. As described earlier, the LiveData component can be used as a wrapper around data values within a view model. Once contained in a LiveData instance, those variables become observable to other objects within the app, typically UI controllers such as Activities and Fragments. This allows the UI controller to receive a notification whenever the underlying LiveData value changes. An observer is set up by creating an instance of the Observer class and defining an <em>onChange()<\/em> method to be called when the LiveData value changes. Once the Observer instance has been created, it is attached to the LiveData object via a call to the LiveData object\u2019s <em>observe()<\/em> method.<\/p>\n\n\n\n<p>LiveData instances can be declared mutable using the MutableLiveData class, allowing both the ViewModel and UI controller to change the underlying data value.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Adding LiveData to the ViewModel<\/h2>\n\n\n\n<p>Launch Android Studio, open the ViewModelDemo project created in the previous chapter, and open the <em>MainViewModel.java<\/em> file, which should currently read as follows:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> com.ebookfrenzy.viewmodeldemo;\r\n \r\n<span class=\"hljs-keyword\">import<\/span> androidx.lifecycle.ViewModel;\r\n \r\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MainViewModel<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">ViewModel<\/span> <\/span>{\r\n \r\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> Float rate = <span class=\"hljs-number\">0.74F<\/span>;\r\n    <span class=\"hljs-keyword\">private<\/span> String dollarText = <span class=\"hljs-string\">\"\"<\/span>;\r\n    <span class=\"hljs-keyword\">private<\/span> Float result = <span class=\"hljs-number\">0F<\/span>;\r\n \r\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">setAmount<\/span><span class=\"hljs-params\">(String value)<\/span> <\/span>{\r\n        <span class=\"hljs-keyword\">this<\/span>.dollarText = value;\r\n        result = Float.parseFloat(dollarText)*rate;\r\n    }\r\n \r\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Float <span class=\"hljs-title\">getResult<\/span><span class=\"hljs-params\">()<\/span>\r\n    <\/span>{\r\n        <span class=\"hljs-keyword\">return<\/span> result;\r\n    }\r\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This stage in the chapter aims to wrap the <em>result<\/em> variable in a MutableLiveData instance (the object will need to be mutable so that the value can be changed each time the user requests a currency conversion). Begin by modifying the class so that it now reads as follows, noting that an additional package needs to be imported when making use of LiveData:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> com.ebookfrenzy.viewmodeldemo;\r\n \r\n<span class=\"hljs-keyword\">import<\/span> androidx.lifecycle.MutableLiveData;\r\n<span class=\"hljs-keyword\">import<\/span> androidx.lifecycle.ViewModel;\r\n \r\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>  <span class=\"hljs-title\">MainViewModel<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">ViewModel<\/span> <\/span>{\r\n \r\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> Float rate = <span class=\"hljs-number\">0.74F<\/span>;\r\n    <span class=\"hljs-keyword\">private<\/span> String dollarText = <span class=\"hljs-string\">\"\"<\/span>;\r\n    <span class=\"hljs-comment\">\/\/ private Float result = 0F;<\/span>\r\n    <span class=\"hljs-keyword\">final<\/span> <span class=\"hljs-keyword\">private<\/span> MutableLiveData&lt;Float&gt; result = <span class=\"hljs-keyword\">new<\/span> MutableLiveData&lt;&gt;();\r\n \r\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">setAmount<\/span><span class=\"hljs-params\">(String value)<\/span> <\/span>{\r\n        <span class=\"hljs-keyword\">this<\/span>.dollarText = value;\r\n        result = Float.parseFloat(dollarText)*rate;\r\n    }\r\n \r\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Float <span class=\"hljs-title\">getResult<\/span><span class=\"hljs-params\">()<\/span>\r\n    <\/span>{\r\n        <span class=\"hljs-keyword\">return<\/span> result;\r\n    }\r\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now that the result variable is contained in a mutable LiveData instance, both the <em>setAmount()<\/em> and <em>getResult() <\/em>methods must be modified. In the case of the <em>setAmount() <\/em>method, a value can no longer be assigned to the result variable using the assignment (=) operator. Instead, the LiveData <em>setValue()<\/em> method must be called, passing through the new value as an argument. As currently implemented, the <em>getResult()<\/em> method is declared to return a Float value and must be changed to return a MutableLiveData object. Making these remaining changes results in the following class file:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> com.ebookfrenzy.viewmodeldemo;\r\n \r\n<span class=\"hljs-keyword\">import<\/span> androidx.lifecycle.MutableLiveData;\r\n<span class=\"hljs-keyword\">import<\/span> androidx.lifecycle.ViewModel;\r\n \r\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>  <span class=\"hljs-title\">MainViewModel<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">ViewModel<\/span> <\/span>{\r\n \r\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> Float rate = <span class=\"hljs-number\">0.74F<\/span>;\r\n    <span class=\"hljs-keyword\">private<\/span> String dollarText = <span class=\"hljs-string\">\"\"<\/span>;\r\n    <span class=\"hljs-keyword\">final<\/span> <span class=\"hljs-keyword\">private<\/span> MutableLiveData&lt;Float&gt; result = <span class=\"hljs-keyword\">new<\/span> MutableLiveData&lt;&gt;();\r\n \r\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">setAmount<\/span><span class=\"hljs-params\">(String value)<\/span> <\/span>{\r\n        <span class=\"hljs-keyword\">this<\/span>.dollarText = value;\r\n        <span class=\"hljs-comment\">\/\/ result = Float.parseFloat(dollarText)*rate;<\/span>\r\n        result.setValue(Float.parseFloat(dollarText)*rate);\r\n    }\r\n \r\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Float <span class=\"hljs-title\">getResult<\/span><span class=\"hljs-params\">()<\/span>\r\n    <span class=\"hljs-comment\">\/\/ public MutableLiveData&lt;Float&gt; getResult()<\/span>\r\n    <\/span>{\r\n        <span class=\"hljs-keyword\">return<\/span> result;\r\n    }\r\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Implementing the Observer<\/h2>\n\n\n\n<p>Now that the conversion result is contained within a LiveData instance, the next step is configuring an observer within the UI controller, which, in this example, is the FirstFragment class. Locate the <em>FirstFragment.java<\/em> class (<em>app -&gt; java -&gt; &lt;package name&gt; -&gt; FirstFragment<\/em>), double-click on it to load it into the editor, and modify the <em>onViewCreated()<\/em> method to create a new Observer instance named <em>resultObserver<\/em>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> com.ebookfrenzy.viewmodeldemo;\r\n \r\n<span class=\"hljs-keyword\">import<\/span> androidx.lifecycle.Observer;\r\n.\r\n.\r\n<span class=\"hljs-meta\">@Override<\/span>\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">onViewCreated<\/span><span class=\"hljs-params\">(@NonNull View view, @Nullable Bundle savedInstanceState)<\/span> <\/span>{\r\n    <span class=\"hljs-keyword\">super<\/span>.onViewCreated(view, savedInstanceState);\r\n \r\n    binding.resultText.setText(String.format(Locale.ENGLISH,<span class=\"hljs-string\">\"%.2f\"<\/span>,\r\n            viewModel.getResult()));\r\n \r\n    <span class=\"hljs-keyword\">final<\/span> Observer&lt;Float&gt; resultObserver = <span class=\"hljs-keyword\">new<\/span> Observer&lt;Float&gt;() {\r\n        <span class=\"hljs-meta\">@Override<\/span>\r\n        <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">onChanged<\/span><span class=\"hljs-params\">(@Nullable <span class=\"hljs-keyword\">final<\/span> Float result)<\/span> <\/span>{\r\n            binding.resultText.setText(String.format(Locale.ENGLISH,\r\n                    <span class=\"hljs-string\">\"%.2f\"<\/span>, result));\r\n        }\r\n    };\r\n.\r\n.\r\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <em>resultObserver<\/em> instance declares the <em>onChanged()<\/em> method which, when called, is passed the current result value, which it then converts to a string and displays on the resultText TextView object. The next step is to add the observer to the result LiveData object, a reference that can be obtained via a call to the <em>getResult()<\/em> method of the ViewModel object. Since updating the result TextView is now the responsibility of the <em>onChanged() <\/em>callback method, the existing lines of code to perform this task can now be deleted:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@Override<\/span>\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">onViewCreated<\/span><span class=\"hljs-params\">(@NonNull View view, @Nullable Bundle savedInstanceState)<\/span> <\/span>{\r\n    <span class=\"hljs-keyword\">super<\/span>.onViewCreated(view, savedInstanceState);\r\n \r\n    binding.resultText.setText(String.format(Locale.ENGLISH,<span class=\"hljs-string\">\"%.2f\"<\/span>,\r\n            viewModel.getResult()));\r\n \r\n    <span class=\"hljs-keyword\">final<\/span> Observer&lt;Float&gt; resultObserver = <span class=\"hljs-keyword\">new<\/span> Observer&lt;Float&gt;() {\r\n        <span class=\"hljs-meta\">@Override<\/span>\r\n        <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">onChanged<\/span><span class=\"hljs-params\">(@Nullable <span class=\"hljs-keyword\">final<\/span> Float result)<\/span> <\/span>{\r\n           <span class=\"hljs-comment\">\/\/ binding.resultText.setText(String.format(Locale.ENGLISH,<\/span>\r\n           <span class=\"hljs-comment\">\/\/         \"%.2f\", result));<\/span>\r\n        }\r\n    };\r\n \r\n    viewModel.getResult().observe(getViewLifecycleOwner(), resultObserver);\r\n \r\n    binding.convertButton.setOnClickListener(v -&gt; {\r\n        <span class=\"hljs-keyword\">if<\/span> (!binding.dollarText.getText().toString().equals(<span class=\"hljs-string\">\"\"<\/span>)) {\r\n            viewModel.setAmount(String.format(Locale.ENGLISH,<span class=\"hljs-string\">\"%s\"<\/span>,\r\n                    binding.dollarText.getText()));\r\n            <span class=\"hljs-comment\">\/\/ binding.resultText.setText(String.format(Locale.ENGLISH,\"%.2f\",<\/span>\r\n            <span class=\"hljs-comment\">\/\/        viewModel.getResult()));<\/span>\r\n        } <span class=\"hljs-keyword\">else<\/span> {\r\n            binding.resultText.setText(<span class=\"hljs-string\">\"No Value\"<\/span>);\r\n        }\r\n    });\r\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Compile and run the app, enter a value into the dollar field, click on the Convert button, and verify that the converted euro amount appears on the TextView. This confirms that the observer received notification that the result value had changed and called the <em>onChanged()<\/em> method to display the latest data.<\/p>\n\n\n\n<p>Note in the above implementation of the <em>onViewCreated()<\/em> method that the line of code responsible for displaying the current result value each time the method was called was removed. This was originally put in place to ensure that the displayed value was recovered if the Fragment was recreated for any reason. Because LiveData monitors the lifecycle status of its observers, this step is no longer necessary. When LiveData detects that the UI controller was recreated, it automatically triggers any associated observers and provides the latest data. Verify this by rotating the device while a euro value is displayed on the TextView object and confirming that the value is not lost.<\/p>\n\n\n\n<p>Before moving on to the next chapter, close the project, copy the ViewModelDemo project folder, and save it as ViewModelDemo_LiveData to be used later when saving the ViewModel state.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>This chapter demonstrated the use of the Android LiveData component to ensure that the data displayed to the user always matches that stored in the ViewModel. This relatively simple process consisted of wrapping a ViewModel data value within a LiveData object and setting up an observer within the UI controller subscribed to the LiveData value. Each time the LiveData value changes, the observer is notified, and the <em>onChanged() <\/em>method is called and passed the updated value.<\/p>\n\n\n\n<p>Adding LiveData support to the project has gone some way towards simplifying the design of the project. Additional and significant improvements are also possible using the Data Binding Library, details of which will be covered in the next chapter.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The previous chapter began building an app to conform to the recommended Jetpack architecture guidelines. These initial steps involved implementing the data model for the app user interface within a ViewModel instance. This chapter will further enhance the app design using the LiveData architecture component. Once LiveData support has been added to the project in [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[29,30],"tags":[],"class_list":["post-10302","post","type-post","status-publish","format-standard","hentry","category-android-studio","category-java"],"_links":{"self":[{"href":"https:\/\/www.answertopia.com\/wp-json\/wp\/v2\/posts\/10302","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.answertopia.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.answertopia.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.answertopia.com\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.answertopia.com\/wp-json\/wp\/v2\/comments?post=10302"}],"version-history":[{"count":1,"href":"https:\/\/www.answertopia.com\/wp-json\/wp\/v2\/posts\/10302\/revisions"}],"predecessor-version":[{"id":10303,"href":"https:\/\/www.answertopia.com\/wp-json\/wp\/v2\/posts\/10302\/revisions\/10303"}],"wp:attachment":[{"href":"https:\/\/www.answertopia.com\/wp-json\/wp\/v2\/media?parent=10302"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.answertopia.com\/wp-json\/wp\/v2\/categories?post=10302"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.answertopia.com\/wp-json\/wp\/v2\/tags?post=10302"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}