Communication between activities

Posted by sfhc on Tue, 14 Dec 2021 08:02:35 +0100

Suppose we have a common scenario:

  • There are two activities. The first Activity shows a paragraph of text
  • Click the "Edit" button to start the second Activity, and pass this text as a parameter to the second Activity
  • Edit the string in the second Activity
  • After editing, click Save to return the results to the first Activity
  • The first Activity shows the modified string

As shown below:

This is a very simple and common scenario. We generally pass parameters through startActivityForResult and receive the edited results in onActivityResult. The code is also very simple, as follows:

//The first Activity starts editing the Activity
btnEditByTradition.setOnClickListener {
    val content = tvContent.text.toString().trim()
    val intent = Intent(this, EditActivity::class.java).apply {
        putExtra(EditActivity.ARG_TAG_CONTENT, content)
    }
    startActivityForResult(intent, REQUEST_CODE_EDIT)
}
 //EditActivity returns the edited result
 btnSave.setOnClickListener {
    val newContent = etContent.text.toString().trim()
    setResult(RESULT_OK, Intent().apply {
        putExtra(RESULT_TAG_NEW_CONTENT, newContent)
    })
    finish()
}
//Accept the edited results in the first Activity and display them
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    when (requestCode) {
        REQUEST_CODE_EDIT -> {
            if (resultCode == RESULT_OK && data != null) {
                val newContent = data.getStringExtra(EditActivity.RESULT_TAG_NEW_CONTENT)
                tvContent.text = newContent
            }
        }
        else -> super.onActivityResult(requestCode, resultCode, data)
    }
}

What are the disadvantages of this method?

  1. Scattered code and poor readability
  2. The encapsulation is not complete. The caller needs to go to EditActivity to know what parameters to pass, what type and what key are
  3. The caller needs to know how the EditActivity returns the parameter type and what the key is to correctly resolve
  4. Poor constraints, definitions of various constants (REQUEST\_CODE,PARAM\_KEY, etc.), if the project management is not rigorous, repeated definitions will lead to trouble in later reconstruction and maintenance

Is there a way to solve the above shortcomings? What we expect is:

  1. An Activity that provides some functions externally should have enough encapsulation. The caller can complete the call in one line of code like calling ordinary methods
  2. The parameter list of the method is the parameters that need to be passed to call the service (parameter quantity, parameter type, whether it is required or not)
  3. The return parameter of the method is the return result of the service
  4. The service providing Activity is like a component, which can provide external functions in the form of methods

It is implemented by Kotlin collaboration and an invisible Fragment.

btnEditByCoroutine.setOnClickListener {
    GlobalScope.launch {
        val content = tvContent.text.toString().trim()

        // Call editContent method of EditActivity
        // Content is the content to edit
        // editContent is the edited result
        val newContent = EditActivity.editContent(this@MainActivity, content)

        if (!newContent.isNullOrBlank()) {
            tvContent.text = newContent
        }
    }
}


Through the above code, we can see that the call can be completed through one method, basically realizing the expectations mentioned above. How is the editContent method implemented internally? See the following code:

/**
  * Edit the specified text
  * @param content Text to edit
  *
  * @return Nullable indicates that the edited content is null, indicating that the user cancelled the editing
  */
@JvmStatic
suspend fun editContent(activity: FragmentActivity, content: String): String? =
    suspendCoroutine { continuation ->
        val editFragment = BaseSingleFragment().apply {
            intentGenerator = {
                Intent(it, EditActivity::class.java).apply {
                    putExtra(ARG_TAG_CONTENT, content)
                }
            }
            resultParser = { resultCode, data ->
                if (resultCode == RESULT_OK && data != null) {
                    val result = data.getStringExtra(RESULT_TAG_NEW_CONTENT)
                    continuation.resume(result)
                } else {
                    continuation.resume(null)
                }
                removeFromActivity(activity.supportFragmentManager)
            }
        }
        editFragment.addToActivity(activity.supportFragmentManager)
    }

This needs to be implemented with the help of a "BaseSingleFragment". This is because I cannot violate the rules of ActivityManagerService and still need to implement it through startActivityForResult and onActivityResult. Therefore, we encapsulate this process through an invisible (no interface) Fragment. The code is as follows:

class BaseSingleFragment : Fragment() {


  /**
   * Generate and start the Intent of the corresponding Activity. Because the Activity to be started, how to start and pass parameters are specified, the Intent is implemented by the specific use location
   *
   * The user must implement this lambda, or throw an exception directly
   */
  var intentGenerator: ((context: Context) -> Intent) = {
    throw RuntimeException("you should provide a intent here to start activity")
  }

  /**
   * The result returned by the target Activity is parsed by the specific implementer and returned
   *
   * The user must implement this lambda, or throw an exception directly
   */
  var resultParser: (resultCode: Int, data: Intent?) -> Unit = { resultCode, data ->
    throw RuntimeException("you should parse result data yourself")
  }

  companion object {
    const val REQUEST_CODE_GET_RESULT = 100
  }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val context = requireContext()
    startActivityForResult(intentGenerator.invoke(context), REQUEST_CODE_GET_RESULT)
  }

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_CODE_GET_RESULT) {
      resultParser.invoke(resultCode, data)
    } else {
      super.onActivityResult(requestCode, resultCode, data)
    }
  }


  /**
   * add current fragment to FragmentManager
   */
  fun addToActivity(fragmentManager: FragmentManager) {
    fragmentManager.beginTransaction().add(this, this::class.simpleName)
      .commitAllowingStateLoss()
  }

  /**
   * remove current fragment from FragmentManager
   */
  fun removeFromActivity(fragmentManager: FragmentManager) {
    fragmentManager.beginTransaction().remove(this).commitAllowingStateLoss()
  }
}
Of course, this is a suspend method, java Collaborative process is not supported, but the reality is that many projects have midway integration Kotlin Yes, there are many left over java Code. In this case, we need to provide the corresponding code java Is it true? The answer is no. Java Code can also call suspend Method is called as follows:
btnEditByCoroutine.setOnClickListener((view) -> {
    String content = tvContent.getText().toString().trim();
    EditActivity.editContent(MainActivityJava.this, content, new Continuation<String>() {
        @NotNull
        @Override
        public CoroutineContext getContext() {
            return EmptyCoroutineContext.INSTANCE;
        }

        @Override
        public void resumeWith(@NotNull Object o) {
            String newContent = (String) o;
            if (!TextUtils.isEmpty(content)) {
                tvContent.setText(newContent);
            }
        }
    });
});

Although the result is received in the resumeWith method through callback, it is much better than the method of startActivityForResult.

Perfect!!!

The inspiration for this implementation comes from RxPermission's implementation of permission application process. Thank you for RxPermission. In addition, glide 3 The start, pause, and unbind of the image loading task and the life cycle of the Activity in version x are also realized by adding a hidden Fragment to the Fragment manager.

This article is transferred from https://juejin.cn/post/7033598140766912549 , in case of infringement, please contact to delete.

Topics: Android