JetPack project – music player

1 more: as a preliminary construction

  • Photo preparation:

    • The picture is the picture of the web page, can be changed; It should load the URL of the image
  • Summary:

    • MVVM– > Implement one Activity+ multiple fragments

      • Activities only do controls
    • All layout files are handled by DataBinding

    • Involved in the library: the ViewModel, LiveData, LifeCycle, DataBinding, Navigation + MVVM

    • Each Activity/Fragment corresponds to a ViewModel(the one that stores data), which is then used to call the repository layer

      • MainActivityViewModel
      • The layout file activity_main.xml corresponding to MainActivity is handed over to DataBinding for management
  • The disadvantages of Kotlin

    • Kotlin’s annotations are not perfect — they are usually Java annotations

    • Kotlin’s reflection is imperfect

      • Extra space, 1.6MB
      • High learning cost
      • Slower than Java, but it works
  • Layout Settings: Handed over to dataBinding for management

    • Summary:

      • Every layout has an Activity, which corresponds to a ViewModel
  • Project structure:

    • App domain: project-led business code
    • Architecture: Some common support, a collection of common utility classes for projects

The specific process

  1. First write MainActivity as the control layer, there is no code

  2. Write the layout file activity_main.xml to DataBinding for management (turn on the switch and click the layout TAB to convert)

    1. Give it to DataBinding for management

      • Turn on the DataBinding switch and the layout is transformed
      • There is a ViewModel that manages the data source for the entire MainActivity (a layout has at least one ViewModel)
    2. The drawer is control

      • Because drawer controls can only slide left and right: DrawerLayout
      • But I need to scroll up and down, so I have SlidingUpPanelLayout.
      • The drawer control has a ViewModel value that allows the drawer to open/close + open/close the drawer
    1. Fragment introduced: Follow the development mode of multiple fragments for one Activity:

      • Specifically, three fragment tags are introduced in activity_main.xml
      1. The whole big Fragment, wrapped up RecycleView
      2. That play bar down there, if you click it, it’ll pop up
      3. Fragment: Handles drawer controls
    2. Introduce Navigation for view Navigation: Navigation is intended to be used as Fragment Navigation

      • All three fragments have the following statement, indicating that all three fragments are assigned to Navigation

         android:name="androidx.navigation.fragment.NavHostFragment"
        Copy the code
      • Each Fragment has its own navigation relationship

        • Code diagram:
         app:navGraph="@navigation/nav_drawer" 
        Copy the code
        • Specific navigation relationship: drop it in app/ RES /navigation

      • The layout file loaded in app/ RES /navigation is used to display the interface

      • Add the fragment tag to the layout file in the app/res/navigation directory. The fragment tag introduces the layout file (Tools :layout=”@layout/fragment_main”)

  3. Start writing your home page Fragment: MainFragment

    1. Create the appropriate layout file: fragment_main.xml

      • Give it to DataBinding for management
    2. Fully use DataBinding for layout management:

      • Get the home page layout: fragment_main.xml:

        private var mainBinding: FragmentMainBinding? = null// This is fragment_main.xmlCopy the code
    3. Create a mainViewModel for the home page: Each fragment has a ViewModel

      • MainViewModel: Used to manage data on the home page

        Location: package com. Xiangxue. Puremusic. Bridge. The state private var mainViewModel: mainViewModel? = null // Home Fragment ViewModel todo Status ViewModelCopy the code

        To summarize: this is deeply nested

        MainActivity — – > activity_main. XML – > drawer controls (or so) – > drawer controls (top and bottom) – > page fragments tags – > nav_main – > res/navigation/nav_main XML – < span style = “box-sizing: border-box; color: RGB (74, 74, 74); line-height: 20px; font-size: 14px! Important; white-space: normal;”

        If there are three fragments, there will be three flows

        To summarize again: the overall page logic of the project

        1. Use Navigation to handle Navigation in activity_main.xml

        2. There are multiple fragments in the corresponding nav_xxx. XML

          • DataBinding the contents of the file processing page for each Fragment layout
          • Each of these fragments has its own ViewModel for managing data

      There are 4 fragments, 4 layout files and 4 ViewModels in the project

  4. Introducing base classes:

    • Cause: Because MainActivity doesn’t inherit from Activity, the Activity doesn’t implement the interface that LifeCycle can be used
    • Solve: design the base class BaseActivity, BaseFragment; These two base classes provide a lot of utility functions, template design ideas
    1. BaseActivity:

      • Inherit AppCompatActivity: Because AppCompatActivity’s grandparent class ComponentActivity implements LifeCycle interface, you need to use this pointer in development to hold the environment

      • Open removes final modifiers: to be inherited by other classes

         open class BaseActivity : AppCompatActivity() 
        Copy the code
      • LifeCycle is registered because all subsequent activities will inherit BaseActivity as the observed

        // This BaseActivity is the observed; NetworkStateManager for observer / / eyes from look forward, after a bit like a static inner class implements the singleton pattern lifecycle. The addObserver (NetworkStateManager. Instance)Copy the code
    2. BaseFragment: The parent class of all fragments

      • Fragment holds the Activity environment:
  5. Introducing utility classes:

    • Utils inside architecture:

      • There are many utility classes: for example, replacing the Bar above
    • The binding package inside the architecture

      • Extensive use of the @BindingAdapter annotation: solves the problem of writing logic in controls

        • When using DataBinding on Google’s website, the logic is written into THE XML, resulting in high coupling: how can logic be written into controls?
        • Benefits of using BindingAdapter: Reduces the stress of DataBinding
      • When assigning a reference to a custom field as a ViewModel:

        • This will trigger the Binding utility class and go directly to public static void setViewBackground, where business code can be written
    • Introduction of adapters: RecycleView was extensively used in the project

    • Data/Manager: Monitoring the status of the entire project using LifeCycle: as an extension

      • For example, when the page is visible and the network and other systems have changed, the means of service can be used to prompt or handle

      • The network does not give power, low power

      • Concrete implementation: relying on LifeCycle; Don’t waste performance when the interface is invisible

        • Invisible, no hint
        • Register broadcast when the interface is visible;
        • Remove broadcast when the interface is not visible.
  6. Rich tool classes to eliminate data stickiness: UnPeekLiveData

    • If not solved, there will be data inversion; Use reflection to dynamically modify the code to align its states

    • It’s just going to execute this if statement

       if (observer.mLastVersion >= mVersion) {
           return;
       }
       observer.mLastVersion = mVersion;
       observer.mObserver.onChanged((T) mData);
      Copy the code
  7. MSharedViewModel: Kill EventBus;

    • Because EventBus has a lot of Bean classes, it is not very traceable

    • But LiveData is very trackable:

      • Only setValue, observe: good to check when there is a problem
    • In addition, you can have all activities/fragments share data

      • If you touch the mSharedViewModel, all activities/fragments associated with it will change. This will solve the problem of consistency. If the interface is visible, it will update. But it changes when it is visible; This ViewModel addresses data storage and consistency issues; This is the most important thing

        • Will solve pressing will change the cover, pressing will change the name of the song and so on
      • Ensure that the mSharedViewModel singleton is getAppViewModelProvider

      • This is paired with a factory method: make sure the ViewModel is unique;

      • How to ensure that the shared ViewModel is unique?

  8. Kotlin’s comment question:

    • Slower than Java, with megabytes of space, packages, and Kotlin’s own API; However, Kotlin’s comments are very useful

The design of the ViewModel

  • A Fragment may have multiple ViewModels

    • Any Fragment must have a ViewModel

      • I’m not going to write any findViewById setContentView layout file and I’m going to put it all in DataBinding’s hands, okay
    • There may be multiple ViewModels: it depends on the requirements, maybe seven or eight viewModels

  • So this is the Standard Google architecture design pattern

2 more

  1. Refine MainActivity: send instructions, sub-fragment to do the work; Of course it can also receive instructions (shared ViewModel drives it to work)

    1. Handling shared ViewModel: Replacing EventBus

      • All activities/fragments can take data from this

      • Use of ViewModel:

        • SetValue: change
        • Obeserver: listening
        • PostValue: executed by an asynchronous thread and eventually called into setValue
        • All of the fields in the ViewModel work for the driver
      • All fields in the ViewModel are handed over to LiveData for management

        • Eliminate data stickiness: Prevent problems caused by data flooding

          • Instead of using MutableLiveData directly, you need to use UnPeekLiveData in the utility class to remove the stickiness of the data
      • Set up listener: Controls sliding up and down

        / / for sliding. AddPanelSlideListener (new PlayerSlideListener (mBinding, sliding)); Val timeToAddSlideListener = UnPeekLiveData<Boolean>() val timeToAddSlideListener = UnPeekLiveData<Boolean>()Copy the code
      • Set Listening: Turn off playback details that pop up

        // In order to close the pop-up menu, click the system Back button or the v in the details can be closed // in the details of the left hand side of the sliding icon (click), and MainActivity Back is set ----> if it is expanded, That also details page shows the val closeSlidePanelIfExpanded = UnPeekLiveData < Boolean > ()Copy the code
      • Extension: What’s going on now, recording the Activity

        / / activities close some records (don't really have found him, what's the egg) val activityCanBeClosedDirectly = UnPeekLiveData < Boolean > ()Copy the code
      • Set Listening: The left sidebar appears

        // openMenu opens the menu when set trigger --> change opendrawer.setValue (aBoolean); Val openOrCloseDrawer = UnPeekLiveData<Boolean>()Copy the code
      • Problem with opening cards: Combination of LiveData and others in this shared ViewModel

        Val enableSwipeDrawer = UnPeekLiveData<Boolean>()Copy the code
    2. Rich MainActivity

      • Add eyes: Filter by observing the fields in the shared ViewModel and indirectly control the controls with the help of the middle ViewModel

      • For example: check whether the menu is open

        1. Observe whether the data in the shared ViewModel has changed: The shared ViewModel has the open and close condition of this menu (it is controlled by a UNPeekLiveData that removes stickiness).
        2. After filtering, the mainActivityViewModel is used to set the values of the relevant fields in the mainActivityViewModel, and the layout (activity_main.xml) attribute corresponding to the mainActivityViewModel is set
        // Indirectly, you can open the menu (view) and do a lot of filtering. This is the real project development, Indirect use mSharedViewModel. OpenOrCloseDrawer. Observe (this, {aBoolean - > mainActivityViewModel!! .opendrawer. value = aBoolean // Trigger to change, -- - > watch (logical) open the menu}) / / indirect XXXX (observed) mSharedViewModel. EnableSwipeDrawer. Observe (this, {aBoolean - > mainActivityViewModel! ! .allowDrawerOpen.value = aBoolean // Trigger drawer control associated value})Copy the code
      • Overwrite the Back key: click to put the playback details page down

        / * * * https://www.jianshu.com/p/d54cd7a724bc * when press the back button in the Android calls to onBackPressed () method, * onBackPressed relative to finish method, There are other operations that * involve the state of the Activity, so the call needs to be treated with caution. */ override fun onBackPressed() { // super.onBackPressed(); MSharedViewModel. CloseSlidePanelIfExpanded. Value = true / / trigger change}Copy the code
      • Determine whether the current Activity is actually presented to the user

        • Rewrite this function: onWindowFocusChanged

          /** * https://www.cnblogs.com/lijunamneg/archive/2013/01/19/2867532.html * this onWindowFocusChanged refers to the Activity or loses focus Will call. * You can use this if you want to create an Activity that triggers something as soon as it finishes loading. * entry: onStart---->onResume---->onAttachedToWindow----------->onWindowVisibilityChanged--visibility=0---------->onWindowFocusCh anged(true)-------> * @param hasFocus */ override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (! IsListened) {mSharedViewModel. TimeToAddSlideListener. Value = true / / trigger change isListened = true}}Copy the code
    3. Processing MainActivityViewModel

      • It is the properties in the layout file activity_main.xml that are controlled

      • Added comments to remove openDrawer’s getter (since the variable is val, there is no set function, if it is of type var; It cancels out the get/set function), no

        @jvmField val openDrawer = MutableLiveData<Boolean>()Copy the code
  2. Let’s deal with some minor details in activity_main.xml

    • Deal with this thing

    • This allowDrawerOpen and isOpenDrawer is something in DataBinding: abstracts out the BindingAdapter, and the BindingAdapter pulls out the functionality, pulls out the functionality: independent, reusable

      • To support this BindingAdapter:

        • Need to add this in build.gradle: kotlin-kapt (annotation handler in Kotlin)
        package com.xiangxue.puremusic.data.binding import androidx.core.view.GravityCompat import Androidx. Databinding. BindingAdapter import androidx. Drawerlayout. Widget. Drawerlayout / * * * TODO the students must look oh, can I know why, So many students have been compiling failed, all kinds of errors, the real reason is in here oh, here is the binding with the layout * note: The use of this class, */ object DrawerBindingAdapter {// Open and close drawers @jvmstatic @BindingAdapter(value = ["isOpenDrawer"], requireAll = false) fun openDrawer(drawerLayout: DrawerLayout, isOpenDrawer: Boolean) { if (isOpenDrawer && ! drawerLayout.isDrawerOpen(GravityCompat.START)) { drawerLayout.openDrawer(GravityCompat.START) } else { }} @jvmStatic @bindingAdapter (value = ["allowDrawerOpen"],  requireAll = false) fun allowDrawerOpen(drawerLayout: DrawerLayout, allowDrawerOpen: Boolean) { drawerLayout.setDrawerLockMode(if (allowDrawerOpen) DrawerLayout.LOCK_MODE_UNLOCKED else DrawerLayout.LOCK_MODE_LOCKED_CLOSED) } }Copy the code
    • About images in the interface: Simulating JSON parsing

  1. Now deal with the problem of the navigation diagram

    1. Sub-fragment in navigation diagram: MainFragment

      • Looking for a location: activity_main.xml—>fragment:navGraph=”@navigation/nav_main” —>nav_main.xml—>android:name=”com.xiangxue.puremusic.ui.page.MainFragment”

      • Handling viewModels: Write multiple viewModels, with different packages, depending on the functionality of the ViewModel

      // Use Databindxxx private var mainBinding: FragmentMainBinding? //mainViewModel handles only the state of the Fragment; Private var mainViewModel: mainViewModel? = null // Fragment todo Status ViewModel //musicRequestViewModel: This ViewModel is the private var musicRequestViewModel for handling requests: musicRequestViewModel? = null // VM todo Request ViewModel for music resourcesCopy the code
      • RecycleView interface is used

        • Add the adapter
      // adapter private var adapter: SimpleBaseBindingAdapter<TestMusic? , AdapterPlayItemBinding? >? = nullCopy the code
      • Initialize the ViewModel
      override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) // Initialize ViewModel mainViewModel = getFragmentViewModelProvider(this).get(MainViewModel::class.java) musicRequestViewModel = getFragmentViewModelProvider(this).get(MusicRequestViewModel::class.java) }Copy the code
      • DataBinding associated ViewModel
      override fun onCreateView( inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle? ) : View { val view: View = inflater.inflate(R.layout.fragment_main, container, False) mainBinding = FragmentMainBinding. Bind (view) mainBinding?. Click = ClickProxy() SetVm (mainViewModel) // Set the VM to change the data in real time. Return view}Copy the code
      • Inside this is a ClickProxy() that handles all click events, and the Fragment environment would be difficult to replace with a ViewModel

        Inner class ClickProxy {// When clicking "Menu" on the home page, Direct navigation interface - > menu fragments of fun openMenu () {sharedViewModel. OpenOrCloseDrawer. Value = true} / / / / trigger when click "search" icon on the front page, Navigate to the searchFragment interface fun search() = nav().navigate(r.i.a.ction_mainFragment_to_searchfragment)}Copy the code
  2. Now work on the MainFragment layout file

    • This is administered by DataBinding, so the layout can be divided into DataBinding areas and UI areas

      • DataBinding area:

        <data> <! - click event - > < variable name = "click" type = "com. Xiangxue. Puremusic. UI. Page. MainFragment. ClickProxy" / > < variable name = "vm" type="com.xiangxue.puremusic.bridge.state.MainViewModel" /> </data>Copy the code
      • Dealing with UI areas: Too much code

        1. The region contained in the image above

          1. Collapse toolbar: Three buttons

            • Open the scroll menu

            • Open the search page: Use Navigation

          Handle the execution of the click event: open the scroll menu

          1. Now the page of the total control of the click to register (part of the code) in fact on the line

            Inner class ClickProxy {// When clicking "Menu" on the home page, Direct navigation interface - > menu fragments of fun openMenu () {sharedViewModel. OpenOrCloseDrawer. Value = true} / / trigger}Copy the code
          2. There will be eyes in MainActivity to see, execute the corresponding logic

          3. There are eyes to see: to open the menu

          Handle click events: Open the search page

          1. Add code to the listening master control of the page

            Inner class ClickProxy {// When the search icon is clicked on the home page, Navigate to the searchFragment interface fun search() = nav().navigate(r.i.a.ction_mainFragment_to_searchfragment)}Copy the code
          2. Jump in the BaseFragment

            /** * Navigate to the fragment for all sub-fragments; Encapsulates the Navigation * @ return * / protected fun nav () : NavController {return NavHostFragment. FindNavController (this)}Copy the code
          3. Note that the Navigation id is specified: navigationR.id.action_MainFragment_to_searchFragment

        2. ToolBar: This is a clickable ToolBar that leads to different menus

          • A ViewModel

          • Code:

            */ class MainViewModel : ViewModel() {// ObservableBoolean or LiveData // ObservableBoolean prevents jitter, frequent changes, Use this scenario // LiveData instead // MainFragment to initialize the page markup for example: Set Val initTabAndPage = ObservableBoolean() // MainFragment "Best Practices" in the WebView need to load the page link path @JvmField val pageAssetPath = ObservableField<String>() }Copy the code

            Prevent jitter: The interface is constantly updated, for example, the downloaded progress bar

            - Although no images are loaded, there is still a large memory overhead problem - Conclusion: Use Observables instead of LiveData when interface updates are frequent - saves running memory - because LiveData is expensive - Observables belong to DataBinding, which is older than LiveData and has a long history of optimizationCopy the code
        3. ViewPager1: implements page switching

        4. Other Information Page

          <! - 2. "other information area" is actually a WebView showed only web information - > < androidx. Core. Widget. NestedScrollView android: layout_width = "match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/web_view" pageAssetPath="@{vm.pageAssetPath}" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#B4D9DD" android:clipToPadding="false" android:visibility="visible" /> </androidx.core.widget.NestedScrollView>Copy the code

          How is this function implemented?

          • Pull it into the BindingAdapter: the advantage of using DataBinding extensively

          • Load a local page:

            @jVMStatic @SuppressLint("SetJavaScriptEnabled") @bindingAdapter (value = ["pageAssetPath"], requireAll = false) fun loadAssetsPage(webView: WebView, assetPath: String) { webView.webViewClient = object : WebViewClient() {Copy the code
          • Loading the network page:

            @suppressLint ("SetJavaScriptEnabled") @bindingAdapter (value = ["loadPage"], requireAll = false) fun loadPage(webView: WebView, loadPage: String?) { webView.webViewClient = WebViewClient() webView.scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAYCopy the code
        5. Recyclerview: Listitem

          • Code display:
          < androidx. Recyclerview. Widget. Recyclerview 1 · the garage android: id = "@ + id/rv" android: layout_width = "match_parent" Android :layout_height="match_parent" Android :clipToPadding="false" Android :visibility="visible app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" tools:listitem="@layout/adapter_play_item" />Copy the code
          • In fact, you lost a layout file: you still need to hand it over to DataBinding for management (for later extension).

        6. Add TabLayout corresponding to DataBinding stuff

          object TabPageBindingAdapter { @JvmStatic @BindingAdapter(value = ["initTabAndPage"], requireAll = false) fun initTabAndPage(tabLayout: TabLayout, initTabAndPage: Boolean) { if (initTabAndPage) { val count = tabLayout.tabCount val title = arrayOfNulls<String>(count) for (i in 0 until count) { val tab = tabLayout.getTabAt(i) if (tab ! = null && tab.text ! = null) { title[i] = tab.text.toString() } } val viewPager: ViewPager = tabLayout.rootView.findViewById(R.id.view_pager) if (viewPager ! = null) { viewPager.adapter = CommonViewPagerAdapter(count, false, title) tabLayout.setupWithViewPager(viewPager) } } } @BindingAdapter(value = ["tabSelectedListener"], requireAll = false) fun tabSelectedListener(tabLayout: TabLayout, listener: OnTabSelectedListener?) { tabLayout.addOnTabSelectedListener(listener) } }Copy the code

    \

Processing MainFragment:

  1. Add adapter: process recyclerView: Listitem

    // adapter private var adapter: SimpleBaseBindingAdapter<TestMusic? , AdapterPlayItemBinding? >? = nullCopy the code
  2. Give the adapter_play_item.xml to DataBinding for management and an AdapterPlayItemBinding is generated

    // Set setters (item layout and adapter binding) Adapter = object: SimpleBaseBindingAdapter<TestMusic? , AdapterPlayItemBinding? >(context, R.layout.adapter_play_item) {Copy the code
  3. Do the initialization on onViewCreated

    • Trigger Tablelayout:

      mainViewModel!! .initTabAndPage. Set (true) corresponds to a TablelayoutCopy the code
    • Trigger a WebView:

      // Trigger --> Also load WebView mainViewModel!! . PageAssetPath. Set (" the WorkManager JetPack. HTML ")Copy the code
    • Set the adapter (layout of the item and binding of the adapter)

      Adapter = object: SimpleBaseBindingAdapter<TestMusic? , AdapterPlayItemBinding? >(context, R.layout.adapter_play_item) { override fun onSimpleBindItem( binding: AdapterPlayItemBinding? , item: TestMusic? , holder: RecyclerView.ViewHolder? ) { binding ? .tvTitle ? .text = item ? .title // Bind with DataBinding: title binding? .tvArtist ? .text = item ? .artist ? Glide. With (binding?. IvCover!! .context).load(item ? .coverimg).into(binding. IvCover) For future extensions val currentIndex = PlayerManager. Instance. AlbumIndex subscript record / / / song/play tag binding. IvPlayStatus. SetColor (the if (currentIndex == holder?.adapterPosition) resources.getColor(r.color.coloraccent) else color.transparent) The right change status icon is red, and if it doesn't match, Is that there is no / / click on the Item binding. Root. SetOnClickListener {v - > Toast. MakeText (mContext, "play music", Toast.LENGTH_SHORT).show() PlayerManager.instance.playAudio(holder !! .adapterPosition)}}} // Create a RecycleView mainBinding!! .rv.adapter = adapterCopy the code

      Initialization is usually done in overridden methods, otherwise the this pointer will have problems

  4. Add musicRequestViewModel: to handle requests

    • According to the function of the different can do many viewModels; But StateViewModel only has one copy

    • Code examples:

      class MusicRequestViewModel : ViewModel() { var freeMusicsLiveData: MutableLiveData<TestAlbum>? = null get() {if (field == null) {field = MutableLiveData()} return field} set private set fun requestFreeMusics() { HttpRequestManager.instance.getFreeMusic(freeMusicsLiveData) } }Copy the code
  5. Introducing a warehouse layer: This is cumbersome in real development

    • Schematic diagram:

    • Part of the code display:

      Override fun getFreeMusic(LiveData: MutableLiveData<TestAlbum>?) Val gson = gson () val type = object: TypeToken<TestAlbum? >() {}.type val testAlbum = gson.fromJson<TestAlbum>(Utils.getApp().getString(R.string.free_music_json), Type) // TODO can request network here // TODO can request network here // TODO can request database here //..... liveData!! .value = testAlbum }Copy the code
    • In general:

      RequestViewModel calls the repository, which performs a series of operations (parses JSON data, encapsulates it into Javabeans) and gives it to LiveData; Because the LiveData in the repository and the LiveData corresponding to the RequestViewModel are the same; And when you do the lifecycle listening, you’re listening on the requestViewModel, so the UI is refreshed whenever the requestViewModel changes

3 more

  • Why not use a simulator

    • The emulator has a WebView loading failure: the Class cannot be found

      • WebView in emulator loading, Gradle compatibility issues
  • Android native MediaPlayer usage;

  • PlayManager: Some details when playing music

    1. PlayerCallHelper: Automatically coordinates and pauses music when a call comes in. Google doesn’t do it natively, other vendors do
    2. PlayerReceiver: A broadcast used to receive changes (messages from the system, down from the Internet) and perform actions on music
    3. PlayerService: enables background playback
    4. PlayerManager: Handles the details of playback;
  • Navigation2: Play the page, the second Fragment: PlayerFragment;

    1. Initialize ViewModel:

      / / initialize the VM playerViewModel = getFragmentViewModelProvider (this). Get the < playerViewModel > (playerViewModel: : class. Java)Copy the code
    2. Bind DataBinding to ViewModel

      //DataBnding+ViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle?) : View {// load interface val View: View = inflater.inflate(R.layout.fragment_player, container, False) / / Binding Binding Binding = FragmentPlayerBinding. Bind (view) Binding?. Click = ClickProxy () / / a, suspended, Under a binding?. Event = EventHandler () / / drag of the problem, where to play where the binding?. = playerViewModel/vm/ViewModel binding and layout return view}Copy the code
    3. Add eyes: Observe changes in data

      • Share ViewModel listener, add eyes:

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, SavedInstanceState) / / observation sharedViewModel. TimeToAddSlideListener. Observe (viewLifecycleOwner, { if (view.parent.parent is SlidingUpPanelLayout) { val sliding = view.parent.parent as SlidingUpPanelLayout // Add to monitor (up) sliding. AddPanelSlideListener (PlayerSlideListener (binding!!!!! , sliding)) } })Copy the code
        • Realize click the play bar, pop up a page

          1. OnViewCreated method in PlayerFragment

             sharedViewModel.timeToAddSlideListener.observe(viewLifecycleOwner, {
                     if (view.parent.parent is SlidingUpPanelLayout)
            Copy the code
          2. It’s actually inside this: SharedViewModel

          3. It’s actually in MainActivity

            Realize android native future trend: JetPack family bucket +MVVM architecture mode

        • PlayerFragment nested mode: Activity_main.xml –>.SlidingUpPanelLayout App in fragment wrapped with this control :navGraph=”@navigation/nav_slide” / > — > fragments of the android: name = “com. Xiangxue. Puremusic. UI. Page. PlayerFragment” — – > PlayerFragment

        • Add listener according to nested mode:

          Override fun onViewCreated(view: view, savedInstanceState: Bundle?) { super.onViewCreated(view, SavedInstanceState) / / observation sharedViewModel. TimeToAddSlideListener. Observe (viewLifecycleOwner, {/ / according to PlayerFragment nested relations, To monitor it; If (view.parent. Parent is SlidingUpPanelLayout) {val sliding = view.parent. Parent as SlidingUpPanelLayout sliding.addPanelSlideListener(PlayerSlideListener(binding!! , sliding)) } })Copy the code
        • Add listener to SlidingUpPanelLayout class PlayerSlideListener(

        • Add eyes: changeMusicLiveData

          // I am the playback bar, I want to change, I become observers -- -- -- -- -- > broadcast related class PlayerManager PlayerManager. Instance. ChangeMusicLiveData. Observe (viewLifecycleOwner, {changeMusic -> // for example: understand when cutting the song, the title of the music, author, cover status and so on change playerViewModel!! .title.set(changeMusic.title) playerViewModel!! .artist.set(changeMusic.summary) playerViewModel!! .coverImg.set(changeMusic.img) })Copy the code
        • PlayerViewModel: ViewModel only does data management;

          • Now I don’t need LiveData to use ObservableField: BECAUSE I’m still playing songs when I’m not visible
          • */ class PlayerViewModel: ObservableField () {Copy the code
        • Add eyes: Pause play

          / / play/pause icon is a control of true and false PlayerManager. Instance. PauseLiveData. Observe (viewLifecycleOwner, { aBoolean -> playerViewModel!! .isPlaying.set(! aBoolean!!) // Display pause while playing, display play while pausing})Copy the code
        • List loop, single loop, random play: the last line is returned as a value to determine which of the three play modes

          / / list cycle, single cycle, random play PlayerManager. Instance. PlayModeLiveData. Observe (viewLifecycleOwner, {anEnum - > val resultID: Int resultID = the if (anEnum = = = PlayingInfoManager. RepeatMode. LIST_LOOP) {/ / list circulation playerViewModel!!!!! . PlayModeIcon. Set (MaterialDrawableBuilder. IconValue. REPEAT). R.s tring play_repeat/list/cycle} else if (anEnum = = = PlayingInfoManager. RepeatMode. ONE_LOOP) {/ / single cycle playerViewModel!!!!! . PlayModeIcon. Set (MaterialDrawableBuilder. IconValue. REPEAT_ONCE). R.s tring play_repeat_once / / single cycle} else {/ / random cycle playerViewModel!! . PlayModeIcon. Set (MaterialDrawableBuilder. IconValue. SHUFFLE). R.s tring play_shuffle / /} / / real change if random cycle (view.parent.parent is SlidingUpPanelLayout) { val sliding = view.parent.parent as SlidingUpPanelLayout if (sliding. PanelState = = SlidingUpPanelLayout. PanelState. EXPANDED) {/ / when playing a spread, to: this is packaged / / here will pop up: ShowShortToast (resultID)}}})Copy the code
        • When click the Back button: watch this thing closeSlidePanelIfExpanded, because the Back is done twice, to share a ViewModel, literally add fragments after just need to change the state

          / / such as: Scene back. What do you want to do sharedViewModel closeSlidePanelIfExpanded. Observe (viewLifecycleOwner, {if (view.parent. Parent is SlidingUpPanelLayout) {val sliding = view.parent. Parent as SlidingUpPanelLayout That is, Details page displayed the if (sliding. PanelState = = SlidingUpPanelLayout. PanelState. EXPANDED) {sliding. PanelState = SlidingUpPanelLayout. PanelState. COLLAPSED / / narrowed} else {sharedViewModel. ActivityCanBeClosedDirectly. SetValue (true)}}  else { sharedViewModel.activityCanBeClosedDirectly.setValue(true) } })Copy the code
        • Monitoring problems:

          / * * * when we click, we want to trigger * / inner class ClickProxy {/ * public void playerMode () {PlayerManager. GetInstance (). ChangeMode (); }*/ fun previous() = PlayerManager.instance.playPrevious() operator fun next() = PlayerManager.instance.playNext() // Click on the narrow fun slideDown () = sharedViewModel. CloseSlidePanelIfExpanded. SetValue (true) / / more fun more () {} fun togglePlay = ()  PlayerManager.instance.togglePlay() fun playMode() = PlayerManager.instance.changeMode() fun showPlayList() = ShowShortToast showShortToast )}Copy the code
        • Update dragbar progress specific: use inner classes (easy access to the Fragment environment, in DataBinding variables can be placed not only ViewModel but also inner classes)

          Inner class EventHandler: OnSeekBarChangeListener {override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {} override fun onStartTrackingTouch(seekBar: SeekBar) {} PlayerManager override fun onStopTrackingTouch(SeekBar: SeekBar) = PlayerManager.instance.setSeek(seekBar.progress) }Copy the code
    • PlayerViewModel stuff: initialization operation, default play interface

    • There are many fields:

      class PlayerViewModel : ViewModel() {@jvmField = ObservableField<String>() ObservableField<String>() HTPP: XXXX /img0.jpg @jVMField Val coverImg = ObservableField<String>( @jvmField placeHolder = ObservableField<Drawable>() @jVMField val maxSeekDuration = ObservableInt() // The current drag bar progress value @jVMField val currentSeekPosition = ObservableInt() @jVMField Val isPlaying = ObservableBoolean() @jvmField val ObservableField<IconValue>()Copy the code
    • Initialization code:

      // Construct code block, Init {title.set(utils.getApp ().getString(r.string.app_name)) // Default information Artist. set(utils.getApp ().getString(r.sing.app_name)) // Default information PlaceHolder.set(utils.getApp ().resources.getDrawable(r.rawable.bg_album_default)) Placeholder if (PlayerManager. Instance. RepeatMode. = = = PlayingInfoManager repeatMode. LIST_LOOP) {/ / if equal to list "cycle" playModeIcon.set(IconValue.REPEAT) } else if (PlayerManager.instance.repeatMode === PlayingInfoManager. RepeatMode. ONE_LOOP) {/ / if equal to "single cycle" playModeIcon set (IconValue. REPEAT_ONCE)} else { Playmodeicon.set (iconvalue.shuffle) // SHUFFLE}}Copy the code
  • Processing PalyerFragment. XML

    1. Leave it to the DataBinding for management: programming to the ViewModel, not the layout

  • Added custom View: click the play icon and turn the animation in a circle

  • Development mode: DataBinding+ViewModel+BindingAdapter

  • RecycleView, when clicked, the red thing will refresh RecycleView:

    • Refresh the adapter: MainActivity now applies the LiveData feature

      / / play related business data (if the data has changed, in order to better experience). Staring at the PlayerManager instance. ChangeMusicLiveData. Observe (viewLifecycleOwner, {adapter? .notifydatasetChanged () // Update on time})Copy the code
  • Implementation details: Playbar issues:

    • In fragment_player. XML: there is a placeholder problem

    • In the BindingAdapter: this is a place with two fields, the BindingAdapter is still not used. Make sure you study this thing;

    • DataBinding things, Jane’s book four articles, good to learn

      • Greatly reduces stress between activities and fragments and layouts;

4 more

  • API handling: Play with Android apis

  • RxJava implementation was used: Derry ii inside; Don’t know, listen first;

  • Programming to the ViewModel

    • The layout corresponds to at least one ViewModel
    • There can only be one state ViewModel and multiple functions
  • Add a login feature:

    • Summary:

      • The layout is all managed by DataBinding
      • The data is handed to the corresponding ViewModel for processing according to the function
      • The state is handled by the state ViewModel
      • ViewModel to LiveData for management: Fields in ViewModel are managed by LiveData
      • All listening is handled by the inner class (listener class) in the RegisterActivity
    1. Write the RegisterActivity: RegisterActivity

      1. Inherits the base class: BaseActivity

         class RegisterActivity : BaseActivity() 
        Copy the code
      2. Override the onCreate

             override fun onCreate(savedInstanceState: Bundle?) {
                 super.onCreate(savedInstanceState)
        Copy the code
    2. Add the layout file: activity_user_register.xml(hand it over to DataBinding for administration)

      • Focus on the DataBinding area in the layout: you need to add ViewModel: RegisterViewModel; You need to add the listener RegisterActivity ClickClass

      • Focus on the UI area (part) in the layout: Add state ViewModel (registerState)

    3. Process state ViewModel: RegisterViewModel

      • Managing the status of the registration layout: Implements a single principle, regardless of the status (login success or failure) of the request

      • The state ViewModel is unique

      • Code examples:

        // Manage the layout's ViewModel RegisterViewModel class RegisterViewModel: ViewModel() {@jvmField // @jvmField removes getter methods for variables val userName = MutableLiveData<String>() val userPwd = MutableLiveData<String>() val registerState = MutableLiveData<String>() Init {userName. Value = "" userpwd. value = "" registerstate. value = ""}}Copy the code
    4. Register function request server ViewModel: RequestRegisterViewModel

      1. The ViewModel needs to be handed over to LiveData for management: the fields in the ViewModel are all LiveData’s

        LiveData var registerData1: MutableLiveData<LoginRegisterResponse>? = nullCopy the code
        • Note on generic classes: Since we are using android login registration API, the web page will return a JSON data and need to specify the corresponding generic class when using LiveData, so we need to convert this JSON data into a JavaBean class, and use the same JavaBean class for registration and login

        • Note to the following question mark: here we implement manual simulation of lazy loading: it is possible to use native lazy loading, but handwritten

          • Load only when used (lazy load)
          • When used, the get method is called
          • A javaBean must be returned after a successful registration. A failed registration returns a large number of strings
          • There’s no multithreading involved here, so I won’t lock it
          LiveData var registerData1: MutableLiveData<LoginRegisterResponse> ? = null get() {if (field == null) {field = MutableLiveData()} return field} private set // Registration failure status LiveData var registerData2 : MutableLiveData<String> ? = null get() { if (field == null) { field = MutableLiveData() } return field } private setCopy the code
      1. Rich RegisterActivity:

      2. Hide status bar:

        HideActionBar ()// Hide status barCopy the code
      3. Initialize DataBinding:

        RegisterViewModel = getActivityViewModelProvider (this). The get (registerViewModel: : class. Java)/VM/state RequestRegisterViewModel = getActivityViewModelProvider (this). The get (requestRegisterViewModel: : class. Java)/VM/request MainBinding = DataBindingUtil. The setContentView (this, R.l ayout. Activity_user_register) / / initializes the DB mainBinding? .lifecycleOwner = this mainBinding ? .vm = registerViewModel // DataBinding binding ViewModel mainBinding? .click = ClickClass() // Layout creates click eventsCopy the code
      4. Add eyes: Observe the requestViewModel

        // A pair of eyes on the requestRegisterViewModel listen successfully requestRegisterViewModel? .registerData1 ? .observe(this, {registerSuccess(it)}).observe(this, {registerSuccess(it)}) .registerData2 ? .observe(this, { registerFailed(it) })Copy the code
      5. Add two functions: implement a data-driven UI

        • In the layout file there is this: implementation: requestRegisterViewModel? .requestRegister(

        • When the eye sees that the requestViewModel has changed, it calls the corresponding function, which changes the value of the corresponding data (that is, the value of the ViewModel). The layout has the ViewModel, and the data-driven UI is implemented

        fun registerSuccess(registerBean: LoginRegisterResponse?) {// toast.maketext (this, "registered successfully 😀", toast.length_short).show() registerViewModel? .registerState ? .value = "Congratulations ${registerBean? .username} "// TODO registered successfully, Direct access to the login screen The students to write startActivity (Intent (this, LoginActivity: : class. Java))} fun registerFailed (errorMsg: String? {// toast.maketext (this, "failed to register ~ whowow.toast.length_short ", toast.length_short).show() registerViewModel? .registerState ? ${errorMsg}"} ${errorMsg}"}Copy the code
      6. Add listener inner class: Handles all listeners in the layout

        • I need to add inner, Kotlin defaults to public
        Inner class ClickClass {fun registerAction() {if (registerViewModel!! .userName.value.isNullOrBlank() || registerViewModel !! .userPwd.value.isNullOrBlank()) { registerViewModel ? .registerState ? .value = "user name or password is empty, please check" return requestRegisterViewModel? .requestRegister( this@RegisterActivity, registerViewModel !! .userName.value !! , registerViewModel !! .userPwd.value !! , registerViewModel !! .userPwd.value !! ) }}Copy the code
      7. Back to requestRegisterViewModel? RequestRegister belongs to RequestRegisterViewModel

        • When there is no problem with the ViewModel, the repository is called
        fun requestRegister(context: Context, username: String, userpwd: String, reuserpwd: String) {// TODO // can do a lot of things // can omit a lot of code // a lot of validation //.... / / don't have any questions, direct call warehouse HttpRequestManager. The instance. The register (the context, the username, userpwd, reuserpwd, registerData1!! , registerData2 !!) }Copy the code
      8. Call the repository layer: Encapsulate the remote interface at this point

        • Interface suggestion: IRemoteRequest

        • Package details: Ensure a unique copy of LiveData

          • I use LiveData in the ViewModel and I use LiveData in the repository, so I need to make sure I use the same copy of LiveData

          • Just two LiveData in the RequestRegisterViewModel will be passed to the repository layer

          • Remote interfaces in the warehouse layer: This ensures the same copy

      9. After the registration is successful, go to the following page:

        fun registerSuccess(registerBean: LoginRegisterResponse?) {// toast.maketext (this, "registered successfully 😀", toast.length_short).show() registerViewModel? .registerState ? .value = "Congratulations ${registerBean? . The username} user registration, "/ / TODO registered success, direct access to the login screen The students to write startActivity (Intent (this, LoginActivity: : class. Java))}Copy the code

\

  1. Secondary encapsulation of the network model using RxJava:

    • Why Encapsulate: Can the server be messed up

      • Sometimes normal data is returned
      • Sometimes null data is returned
      • Sometimes it even returns null
    • Encapsulation diagram: The custom operators in RxJava implement interception and filtering

    • Add wrapper code: Complete the “internal request through OKHTTP” shown above, which RxJava rewraps and processes inside the asynchronous thread

      Interface WanAndroidAPI {/** * login API * username=Derry- vip&Password =123456 */ @POST("/user/login") @FormUrlEncoded fun loginAction(@Field("username") username: String, @Field("password") password: String) : Observables < LoginRegisterResponseWrapper < LoginRegisterResponse > > / / the return value/register API * / * * * @ POST ("/user/register ") @FormUrlEncoded fun registerAction(@Field("username") username: String, @Field("password") password: String, @Field("repassword") repassword: String) : Observables < LoginRegisterResponseWrapper < LoginRegisterResponse > > / / the return valueCopy the code
    • The RxJava2 custom operator intercepts and filters the server’s content by streaming the wrapped beans down

      1. Implementation interface: This class can then be called a custom operation

        The abstract class APIResponse < T > (val context: context) / / master / / LoginRegisterResponseWrapper < T > = = figure Is this generic packaging Bean: Observer<LoginRegisterResponseWrapper<T>> {Copy the code
      2. The function in RxJava at the time of the initial subscription: implements a popup indicating that it is working, that is, there is a circle going around at the time of registration

        Override fun onSubscribe(d: Disposable) {if (isShow) {loadingdialog.show (context)}}Copy the code
      3. Start intercepting server data

        / / the upstream flow of data I capturing the current layer on layer to packaging Bean = = t: LoginRegisterResponseWrapper < t > override fun onNext (t: LoginRegisterResponseWrapper < T >) {if (t.d ata = = null) {/ / failure failure (" login failed, please check the reason: MSG :${t.errormsg}")} else {// success(t.ata)}}Copy the code
      4. Write the corresponding function:

        (data: T?) Abstract fun failure(errorMsg: String) Override fun onError(e: Throwable) {// Cancel loading loadingdialog.cancel () failure(e.message)} // Stop Override fun onComplete() {// Cancel loading LoadingDialog.cancel() }Copy the code
      5. Handle loading box: custom View, is a turn of the circle

        Loadingdialog.show fun show1() {loadingdialog.show fun (); Static @jvmstatic fun show2() {} private var dialog: dialog? = null fun show(context: Context) { cancel() dialog = Dialog(context) dialog? .setContentView( R.layout.dialog_loading ) dialog? .setCancelable(false) dialog? .setCanceledOnTouchOutside(false) // ..... dialog? .show() } fun cancel() { dialog? .dismiss() } }Copy the code
      6. Implement the specific login process: in HttpRequestManager

        Override fun login(context: context, username: String, password: String, dataLiveData1: MutableLiveData<LoginRegisterResponse>, dataLiveData2: MutableLiveData < String >) {/ / network model APIClient RxJava encapsulation. The instance. InstanceRetrofit (WanAndroidAPI: : class. Java) .loginAction(username, Password). SubscribeOn (Schedulers. IO ()) / / assign the code above a asynchronous thread observeOn (AndroidSchedulers. MainThread ()) / / assign the code below the main thread of android, // dataLiveData1. PostValue (data).subscribe(object: APIResponse<LoginRegisterResponse>(context) { override fun success(data: LoginRegisterResponse?) Callback. LoginSuccess (data) dataLiveData1. Value = data // MVVM, Override fun Failure (errorMsg: String?) override fun Failure (errorMsg: String?) Datalivedata2. value = errorMsg // MVVM}})}Copy the code
      7. Handle listening issues in RegisterActivity:

        • The layout is handed over to DataBinding, where a ViewModel is maintained
        Fun registerAction() {if (registerViewModel!! .userName.value.isNullOrBlank() || registerViewModel !! .userPwd.value.isNullOrBlank()) { registerViewModel ? .registerState ? .value = "user name or password is empty, please check" return requestRegisterViewModel? .requestRegister( this@RegisterActivity, registerViewModel !! .userName.value !! , registerViewModel !! .userPwd.value !! , registerViewModel !! .userPwd.value !! ) }}Copy the code
  2. Handle the login function: LoginActivity

    1. Inheritance BaseActivity:

    2. Add the login layout file: activity_user_login. XML

      • There is a ViewModel to manage the data, and the entire layout is managed by DataBinding

      • There are corresponding click events in the layout for:

         android:onClick="@{()->click.startToRegister()}"
        Copy the code
    3. Get the Binding and ViewModel

      var mainBinding: ActivityUserLoginBinding? Var loginViewModel: loginViewModel? = null // ViewModel var requestLoginViewModel : RequestLoginViewModel? = null // TODO Reqeust ViewModelCopy the code
    4. Write ViewModel: LoginViewModel, which belongs to state ViewModel

      LoginViewModel: LoginViewModel: ViewModel() {@jvmField // @jvmField removes getter methods for variables val userName = MutableLiveData<String>() val userPwd = MutableLiveData<String>() val loginState = MutableLiveData<String>() init { userName.value = "" userPwd.value = "" loginState.value = "" } }Copy the code
    5. Write ViewModel: RequestLoginViewModel

      Class RequestLoginViewModel: ViewModel() {// Request successful ViewModel var registerData1: MutableLiveData<LoginRegisterResponse>? = null get() { if (field == null) { field = MutableLiveData<LoginRegisterResponse>() } return field } private set ViewModel var registerData2: MutableLiveData<String>? = null get() { if (field == null) { field = MutableLiveData<String>() } return field } private setCopy the code
    6. Perfect the onCreate function in LoginActivity

      override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) // Hide status bar hideActionBar() // Initialize operation: LoginViewModel = getActivityViewModelProvider (this). The get (loginViewModel: : class. Java) / / State ViewModel initialization requestLoginViewModel = getActivityViewModelProvider(this).get(RequestLoginViewModel::class.java) // Request The ViewModel initialization mainBinding = DataBindingUtil. The setContentView (this, R.layout.activity_user_login) // DataBinding initializes mainBinding? .lifecycleOwner = this mainBinding ? .vm = loginViewModel // Bind ViewModel to DataBinding with mainBinding? .click = ClickClass() // DataBinding associated click event // successful login eyes listening successfully, save Session requestLoginViewModel before switching to the home page? .registerData1 ? .observe(this, { loginSuccess(it !!) }) // failed to log in to requestLoginViewModel? .registerData2 ? .observe(this, { loginFialure(it !!) })}Copy the code
    7. Function supplement:

      Fun loginSuccess(registerBean: LoginRegisterResponse?) {// toast.maketext (this@LoginActivity, "login successful 😀", toast.length_short).show() loginViewModel? .loginState? .value = "Congratulations ${registerBean? .username} user, login successful "// login successful Need to keep the login session/login/save temporary session information mSharedViewModel. Session. Value = session (true, StartActivity (Intent(this@LoginActivity, MainActivity::class.java))} fun loginFialure(errorMsg:) String?) {// toast.maketext (this@LoginActivity, "login failed ~ whooooo ", toast.length_short).show() loginViewModel? .loginState ? ${errorMsg}"} ${errorMsg}"}Copy the code
      • So this is where you have your data-driven UI, and when you login fails, loginViewModel, right? .loginState ? .value assignment, which is present in activity_user_login. XML

        <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#f00" Android :layout_gravity="center" /> Android :layout_gravity="center" />Copy the code
    8. Add click function:

      Inner class ClickClass {// Click the event, log function fun loginAction() {if (loginViewModel!! .userName.value.isNullOrBlank() || loginViewModel !! .userPwd.value.isNullOrBlank()) { loginViewModel ? .loginState ? .value = "user name or password is empty, please check" return} // non-coroutine version /*requestLoginViewModel? .requestLogin( this@LoginActivity, loginViewModel !! .userName.value!! , loginViewModel !! .userPwd.value!! , loginViewModel !! .userPwd.value!! ) */ / The coroutine version is requestLoginViewModel? .requestLoginCoroutine( this@LoginActivity, loginViewModel !! .userName.value!! , loginViewModel !! .userPwd.value!!) } / / jump to the registration screen fun startToRegister () = startActivity (Intent (this @ LoginActivity RegisterActivity: : class. Java))}Copy the code
    9. Add requestLoginViewModel? .requestLogin

      Fun requestLogin(context: context, username: String, userpwd: String, reuserpwd: String) String) {// TODO // can do a lot of things // can omit a lot of code // a lot of validation //.... HttpRequestManager.instance.login(context, username, userpwd, registerData1!! , registerData2!!) }Copy the code

      You need to define a set of login standards in the IRemoteRequest interface

          fun login(
               context: Context,
               username: String,
               password: String,
               dataLiveData1: MutableLiveData<LoginRegisterResponse>,
               dataLiveData2: MutableLiveData<String>)
      Copy the code

      A concrete implementation of login in HttpRequestManager

      Override fun login(context: context, username: String, password: String, dataLiveData1: MutableLiveData<LoginRegisterResponse>, dataLiveData2: MutableLiveData<String>) {// RxJava encapsulates the network model // If you have an instance of Retrofit, login (), pass in the username and password APIClient.instance.instanceRetrofit(WanAndroidAPI::class.java) .loginAction(username, // Returns the starting point of an Observable's RxJava wrapper, from which it flows down, In the flow to intercept. SubscribeOn (Schedulers. IO ()) / / assign the code above a asynchronous thread. ObserveOn (AndroidSchedulers. MainThread ()) / / assign the code below the main thread / / android DataLiveData1. PostValue (data) // Filter by custom operator. Subscribe (object: APIResponse<LoginRegisterResponse>(context) { override fun success(data: LoginRegisterResponse?) {// callback. LoginSuccess (data) dataLiveData1. Value = data // MVVM} Override fun failure(errorMsg: String?) Datalivedata2. value = errorMsg // MVVM}})}Copy the code

      Process review:

      1. Call: requestLoginViewModel in ClickClass in LoginActivity? RequestLogin (,

      2. Call the requestLogin function in the RequestLoginViewModel to verify that there is no problem and call it to the repository: HttpRequestManager.instance.login(context, username, userpwd, registerData1!! , registerData2!!)

        • Note that the purpose of passing these two LiveData is to share, the two LiveData in RequestLoginViewModel are to share the same two LiveData with () in the repository

          HttpRequestManager. Override fun inside the login

        • When the two LiveData changes, the eyes in the LoginActivity can see this

    1. Save the Session that successfully logged in

      // Save a temporary Session for the login information. Add a field data class Session constructor(val isLogin: Boolean, val loginRegisterResponse: LoginRegisterResponse?)Copy the code
    2. How can I get sessions anywhere and add fields to the shared ViewModel

      • Note that you can’t use LiveData to remove stickiness in this area, you can’t remove stickiness in shared domains

      • Modify the data first, then observe, then you will not receive the previous data, even if the viscosity is excluded; Modifying data before subscribing uses sticky data;

      • So this Session is what you save when you log in

      • MainFragment Displays sessions

        The Intent is written by Kotlin and the source code for the Intent is Java. And Google promises that the source code for the FrameWork layer will not be modified;

  • RxJava need to figure

    • Scenario When the server returns data randomly, customize the RxJava operator to solve the problem;