Skip to content
This repository has been archived by the owner on Jun 7, 2020. It is now read-only.

[NEW] Send dynamicLinks with invites; Support the handling of dynamicLinks in the app. #2196

Merged

Conversation

ear-dev
Copy link
Contributor

@ear-dev ear-dev commented Apr 4, 2019

@RocketChat/android

Changes:

  • Adds support for Firebase DynamicLinks as discussed in the forum here: https://forums.rocket.chat/t/improved-deep-linking-support/1433
  • This PR replaces the current LoginDeepLinkInfo class with a more generic, DeepLinkInfo class that can handle all of the previous deepLinks supported, as well as DynamicLinks.
  • DynamicLinks needs to be configured in your firebase account for this to work in the dynamicLinks case. If it is not then the default will be same as current behavior in the app.
  • For 'play' builds: Invites via another app will include a dynamicLink that when clicked will bring the recipient to the play store to download the app. Once downloaded and logged in, the app will open immediately to a DM with the sender. If the recipient already has the app installed, clicking the dynamicLink will automatically open the app to a DM with the sender.
  • For 'foss' builds: Invites will auto-populate with the hard-coded link to the play store, as it currently does.

Video Demo here: https://drive.google.com/file/d/1t244q_G4Kls_iWzb4EMqA8eBCg94j1bq/view?usp=sharing

@ear-dev ear-dev changed the title Send dynamicLinks with invites; Support the handling of dynamicLinks in the app. [NEW] Send dynamicLinks with invites; Support the handling of dynamicLinks in the app. Apr 10, 2019
@ear-dev
Copy link
Contributor Author

ear-dev commented Jun 4, 2019

@filipedelimabrito I can fix the conflicts in this PR, but I would prefer to wait until you are ready to review so that we don't keep going around in circles.

This is our Phase 1 to support the Contact Sync feature. Do you think we'll get that feature in the next milestone? We are prepared to make all the PRs and present a design doc if needed. Thanks...

@engelgabriel engelgabriel requested a review from philipbrito June 13, 2019 20:17
@engelgabriel
Copy link
Member

@filipedelimabrito can we give this PR some visibility on what is missing for it to get merged if the conflicts are resolved?

@engelgabriel engelgabriel added this to the 3.5.0 milestone Jun 13, 2019

}.addOnFailureListener {
// Error
Toast.makeText(context, "Error dynamic link", Toast.LENGTH_SHORT).show()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded string! 🛑

Please remove and make use of the strings.xml!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These toast messages were just for debugging. I will remove them and replace with timber logging without string translations. If you would rather we do it a different way please let me know.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timber is enough but if it is there because you are still debugging it I'll make this as a WIP PR, ok?

Anyway, I think that showing an error message to the user when there is a failure would be better. In this case make sure to implement the MessageView on the fragment (if needed) and make use of our ext. function showToast(resId) and/or showToast(message) plus log it with timber.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it supposed to show the deeplink url on the toast in this line (on the addOnSuccessListener):
Toast.makeText(context, newDeepLink, Toast.LENGTH_SHORT).show()
?

Only a toast with the deeplink url? 🤔
If so, better to add more context on the message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought is this.... I don't think we want to show any info to the user about the deepLink. Users have no idea what a deepLink is, and there is no requirement that they do. I added the logging with Timber just to have a record in the server logs for admins as needed. If the deepLink creation fails, then it uses the default message, and the user has not been affected. Thoughts?

FYI - I did push a bunch of updates to the PR and the Toast messages are now removed.

@@ -181,7 +181,7 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
totalSocialAccountsEnabled = getInt(TOTAL_SOCIAL_ACCOUNTS)
isLoginFormEnabled = getBoolean(IS_LOGIN_FORM_ENABLED)
isNewAccountCreationEnabled = getBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED)
deepLinkInfo = getParcelable(DEEP_LINK_INFO)
deepLinkInfo = getParcelable(Constants.DEEP_LINK_INFO)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the Constants object please (a comment was left on it).

@@ -115,7 +115,7 @@ fun newInstance(
putInt(TOTAL_SOCIAL_ACCOUNTS, totalSocialAccountsEnabled)
putBoolean(IS_LOGIN_FORM_ENABLED, isLoginFormEnabled)
putBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED, isNewAccountCreationEnabled)
putParcelable(DEEP_LINK_INFO, deepLinkInfo)
putParcelable(Constants.DEEP_LINK_INFO, deepLinkInfo)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the Constants object please (a comment was left on it).

@philipbrito philipbrito changed the title [NEW] Send dynamicLinks with invites; Support the handling of dynamicLinks in the app. [WIP][NEW] Send dynamicLinks with invites; Support the handling of dynamicLinks in the app. Jun 21, 2019
@ear-dev
Copy link
Contributor Author

ear-dev commented Jun 25, 2019

Hi Filipe..... I did manage to test this end to end using our Firebase dynamic links... I'm pretty much done with this PR, unless you have some suggestion about how to best handle the Constants.kt file (I posed the question in the thread)... Thanks.

Let me know if there are other issues you would like resolved before merge...

@philipbrito
Copy link
Contributor

@ear-dev It is not ready to be merged yet. There is still some hard-coded strings on this PR making it unready to be integrated here - for instance.

@ear-dev
Copy link
Contributor Author

ear-dev commented Jun 26, 2019

Thanks Filipe. I see that there are new conflicting files.... I'll fix those. That const you pointed out is fixed.... let me know if you have any thoughts about the Constants.kt file question I posed....

private inline fun Uri.isWebSchemeRoomLink(): Boolean {
val roomType = path.split("/")[1]
if (scheme.startsWith("http") &&
(roomType == "channel" || roomType == "group" || roomType == "direct"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can make use of roomTypeOf(roomType) here to check is the room type is channel, group, etc.

Ex.:

... roomTypeOf(roomType) is RoomType.DirectMessage

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@filipedelimabrito will roomTypeOf() just accept the lower case strings we've parsed from the path? Or does it need to be converted into some object or capitalized string? It's not clear to me right away. Thanks.

allowStateLoss = true
) {
chat.rocket.android.authentication.server.ui.newInstance()
private fun routeNoLink() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ear-dev Can you add a comment to make it clear to anyone reading that code or change the function name to something more meaningful? It will help us a lot here.

@@ -0,0 +1,5 @@
package chat.rocket.android.helper

object Constants {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me take a look in a deep way here. But since we are going to use a Constant class with, well... our constants we will need to move all the stuffs declared on our code here to avoid people declaring it in multiple places of our code for instance.

@ear-dev
Copy link
Contributor Author

ear-dev commented Jun 26, 2019

Thanks for thinking about the constants.kt issue..... I honestly do not know what the best practice should be in this case. Most of your constants are scoped to just a single class I believe. Are there any other examples of a constant like this one that is shared among multiple classes and activities?

*
* @param inviteType The method of invite to log, currently only 'share' using the share intent.
*/
fun logInviteSent(inviteType: String, inviteSucceeded: Boolean) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make the inviteType an enum here.

fun saveDeepLinkInfo(deepLinkInfo: DeepLinkInfo) {
savedDeepLinkInfo = deepLinkInfo
}
fun toOnBoarding() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing white line above function.


private const val DEEP_LINK_INFO = "DeepLinkInfo"
fun newInstance(deepLinkInfo: DeepLinkInfo?): ServerFragment {
val fragment = ServerFragment()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code can be improved here. I'll work on it.

putExtra(Intent.EXTRA_TEXT, getString(R.string.play_store_link))
startActivity(Intent.createChooser(this, getString(R.string.msg_share_using)))
}
// We can't know for sure at this point that the invitation was sent successfully since they will now be outside our app
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. But having it logged by our analytic tool will let us know how many people do this action even though the invitation was not sent successfully as explained on your comment above (thanks for adding it). This way I'll remove the inviteSucceeded which indicate if the invitation was sent successfully or not - since we don't know about it.

DeepLinkInfo(url, userId, token, null, null, null)
} catch (ex: Exception) {
Timber.d(ex, "Error parsing auth deeplink")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For error used the proper Timber function: e.

/**
* Logs the invitation sent event.
*
* @param inviteType The method of invite to log, currently only 'share' using the share intent.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the inviteSucceeded param?

@@ -73,6 +73,13 @@ class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) :
})
}

override fun logInviteSent(inviteType: String, inviteSucceeded: Boolean) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to log it on AnswersAnalytics too...

import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.server.ui.changeServerIntent
import chat.rocket.android.util.extensions.addFragmentBackStack
import chat.rocket.android.util.extensions.toPreviousView
import chat.rocket.android.webview.ui.webViewIntent

class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
var savedDeepLinkInfo: DeepLinkInfo? = null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable can be private.

intent?.let {
val deepLinkInfo = it.getParcelableExtra<DeepLinkInfo>(Constants.DEEP_LINK_INFO)
if (deepLinkInfo != null) {
val chatRoomsFragment = supportFragmentManager.findFragmentByTag(TAG_CHAT_ROOMS_FRAGMENT) as ChatRoomsFragment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, respect our code style.

fun saveDeepLinkInfo(deepLinkInfo: DeepLinkInfo) {
savedDeepLinkInfo = deepLinkInfo
}
fun toOnBoarding() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, where is this function being called?

@philipbrito philipbrito changed the title [WIP][NEW] Send dynamicLinks with invites; Support the handling of dynamicLinks in the app. [NEW] Send dynamicLinks with invites; Support the handling of dynamicLinks in the app. Jun 29, 2019
Copy link
Contributor

@philipbrito philipbrito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ear-dev I have fixed some issues here. Thanks for sending this PR!

@philipbrito philipbrito merged commit 2e3fcc3 into RocketChat:develop Jun 29, 2019
@ear-dev
Copy link
Contributor Author

ear-dev commented Jul 1, 2019

@filipedelimabrito Awesome!! Thanks!!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants