Skip to content

Commit 8d9dcef

Browse files
author
Hussein Aladeen
committed
refactor(DeepLinking): Add support for multi module projects
1 parent b93dc0c commit 8d9dcef

File tree

13 files changed

+263
-340
lines changed

13 files changed

+263
-340
lines changed

extra/gradle/libraries.gradle

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ ext {
3030
mockitoVersion = '1.10.19'
3131

3232
javaPoetVersion = '1.8.0'
33+
autoCommon = '0.8'
34+
autoService = '1.0-rc3'
3335

3436

3537
libraries = [
@@ -39,7 +41,10 @@ ext {
3941

4042
rxJava : "io.reactivex.rxjava2:rxjava:${rxJavaVersion}",
4143
rxAndroid : "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}",
42-
javaPoet : "com.squareup:javapoet:${javaPoetVersion}"
44+
javaPoet : "com.squareup:javapoet:${javaPoetVersion}",
45+
46+
autoCommon : "com.google.auto:auto-common:${autoCommon}",
47+
autoService : "com.google.auto.service:auto-service:${autoService}"
4348
]
4449

4550
testLibraries = [
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.fueled.flowr.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Created by [email protected] on 05/06/2017.
10+
* Copyright (c) 2017 Fueled. All rights reserved.
11+
*/
12+
@Retention(RetentionPolicy.SOURCE)
13+
@Target(ElementType.TYPE)
14+
public @interface DeepLinkHandler {
15+
}

flowr-compiler/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ version = libraryVersion
77
dependencies {
88
compile fileTree(dir: 'libs', include: ['*.jar'])
99
compile project(path: ':flowr-annotations')
10+
1011
compile libraries.javaPoet
12+
compile libraries.autoCommon
13+
compile libraries.autoService
14+
1115
testCompile testLibraries.hamcrest
1216
testCompile testLibraries.junit
1317
testCompile testLibraries.mockito

flowr-compiler/src/main/java/com/fueled/flowr/compilers/DeepLinkAnnotationCompiler.java

Lines changed: 54 additions & 244 deletions
Large diffs are not rendered by default.

flowr-compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor

Lines changed: 0 additions & 1 deletion
This file was deleted.

flowr-compiler/src/test/java/com/fueled/flowr/compilers/DeepLinkAnnotationCompilerTest.java

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class DeepLinkAnnotationCompilerTest {
1515

1616
private static final String TEST_PACKAGE = "com.fueled.flowr.sample";
1717

18-
DeepLinkAnnotationCompiler compiler;
18+
private DeepLinkAnnotationCompiler compiler;
1919

2020
@Before
2121
public void setup() {
@@ -27,39 +27,4 @@ public void process() throws Exception {
2727

2828
}
2929

30-
@Test
31-
public void generateCanonicalNameOneFragmentTest() throws Exception {
32-
List<String> fragmentList = new ArrayList<>();
33-
fragmentList.add("com.fueled.flowr.sample.DemoFragment");
34-
String handlerPackage = compiler.generateCanonicalName(fragmentList);
35-
assertEquals(handlerPackage, TEST_PACKAGE);
36-
}
37-
38-
@Test
39-
public void generateCanonicalNameTwoFragmentSamePackageTest() throws Exception {
40-
List<String> fragmentList = new ArrayList<>();
41-
fragmentList.add(TEST_PACKAGE + ".DemoFragment");
42-
fragmentList.add(TEST_PACKAGE + ".TestFragment");
43-
String handlerPackage = compiler.generateCanonicalName(fragmentList);
44-
assertEquals(handlerPackage, TEST_PACKAGE);
45-
}
46-
47-
@Test
48-
public void generateCanonicalNameTwoFragmentDifferentPackageTest() throws Exception {
49-
List<String> fragmentList = new ArrayList<>();
50-
fragmentList.add(TEST_PACKAGE + ".demo.DemoFragment");
51-
fragmentList.add(TEST_PACKAGE + ".TestFragment");
52-
String handlerPackage = compiler.generateCanonicalName(fragmentList);
53-
assertEquals(handlerPackage, TEST_PACKAGE);
54-
}
55-
56-
@Test
57-
public void generateCanonicalNameTwoFragmentDifferentPackage2Test() throws Exception {
58-
List<String> fragmentList = new ArrayList<>();
59-
fragmentList.add(TEST_PACKAGE + ".demo.DemoFragment");
60-
fragmentList.add(TEST_PACKAGE + ".test.TestFragment");
61-
String handlerPackage = compiler.generateCanonicalName(fragmentList);
62-
assertEquals(handlerPackage, TEST_PACKAGE);
63-
}
64-
6530
}

flowr/src/main/java/com/fueled/flowr/Flowr.java

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@
1313
import android.util.Log;
1414
import android.view.View;
1515

16-
import com.fueled.flowr.internal.FlowrConfig;
1716
import com.fueled.flowr.internal.FlowrDeepLinkHandler;
1817
import com.fueled.flowr.internal.FlowrDeepLinkInfo;
1918
import com.fueled.flowr.internal.TransactionData;
2019

21-
import java.lang.reflect.Constructor;
22-
import java.lang.reflect.InvocationTargetException;
23-
20+
import java.util.ArrayList;
21+
import java.util.Collections;
22+
import java.util.List;
2423

2524
/**
2625
* Created by [email protected] on 31/05/2016.
@@ -29,23 +28,30 @@
2928
public class Flowr implements FragmentManager.OnBackStackChangedListener,
3029
View.OnClickListener {
3130

32-
public final static String DEEP_LINK_URL = "DEEP_LINK_URL";//to be used as Bundle key for deep links.
33-
public final static String FLOWR_CONFIG = "com.fueled.flowr.FlowrConfigImpl"; //Generated file that contain the path to the DeepLinkHandler class.
31+
/**
32+
* To be used as Bundle key for deep links.
33+
*/
34+
public final static String DEEP_LINK_URL = "DEEP_LINK_URL";
3435

3536
private final static String KEY_REQUEST_BUNDLE = "KEY_REQUEST_BUNDLE";
3637
private final static String KEY_FRAGMENT_ID = "KEY_FRAGMENT_ID";
3738
private final static String KEY_REQUEST_CODE = "KEY_REQUEST_CODE";
39+
3840
private final static String TAG = Flowr.class.getSimpleName();
41+
3942
private final FragmentsResultPublisher resultPublisher;
4043
private final int mainContainerId;
44+
4145
private FlowrScreen screen;
4246
private ToolbarHandler toolbarHandler;
4347
private DrawerHandler drawerHandler;
48+
4449
private Fragment currentFragment;
50+
4551
private boolean overrideBack;
4652
private String tagPrefix;
4753

48-
private FlowrDeepLinkHandler deepLinkHandler;
54+
private List<FlowrDeepLinkHandler> deepLinkHandlers;
4955

5056

5157
/**
@@ -117,6 +123,8 @@ public Flowr(@IdRes int mainContainerId, FlowrScreen screen, ToolbarHandler tool
117123
setToolbarHandler(toolbarHandler);
118124
setDrawerHandler(drawerHandler);
119125

126+
deepLinkHandlers = new ArrayList<>();
127+
120128
syncScreenState();
121129
}
122130

@@ -146,16 +154,6 @@ public static ResultResponse getResultsResponse(Bundle arguments, int resultCode
146154
return resultResponse;
147155
}
148156

149-
private Constructor<? extends FlowrDeepLinkHandler> findBindingConstructorForClass() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
150-
//Use reflexion because:
151-
//1. The FlowrDeepLinkHandlerImpl location is dynamic based on the location of all the Fragment that support deep link, so we can't rely on a static position like Dagger.
152-
//2. Because the FlowrDeepLinkHandlerImpl is generated only when a @DeepLink is used, if the project doesn't use deep linking, the class will never be generated and the project won't compile
153-
FlowrConfig config = (FlowrConfig) getClass().getClassLoader().loadClass(FLOWR_CONFIG).getConstructor().newInstance();
154-
Class<?> bindingClass = getClass().getClassLoader().loadClass(config.getHandlerPackage());
155-
//noinspection unchecked
156-
return (Constructor<? extends FlowrDeepLinkHandler>) bindingClass.getConstructor();
157-
}
158-
159157

160158
/**
161159
* Returns the {@link FlowrScreen} used for this router.
@@ -221,6 +219,20 @@ public void setDrawerHandler(DrawerHandler drawerHandler) {
221219
this.drawerHandler = drawerHandler;
222220
}
223221

222+
/**
223+
* Specify a collection of {@link FlowrDeepLinkHandler} to be used when routing deep link
224+
* intents replacing all previously set handlers.
225+
*
226+
* @param handlers the collection of handlers to be used.
227+
*/
228+
public void setDeepLinkHandlers(FlowrDeepLinkHandler... handlers) {
229+
this.deepLinkHandlers.clear();
230+
231+
if (handlers != null) {
232+
Collections.addAll(deepLinkHandlers, handlers);
233+
}
234+
}
235+
224236
/**
225237
* Returns the prefix used for the backstack fragments tag
226238
*
@@ -277,18 +289,20 @@ protected <T extends Fragment & FlowrFragment> void displayFragment(TransactionD
277289
}
278290

279291
/**
280-
* Parse the intent set by {@link TransactionData#deepLinkIntent} and if this intent contains Deep Link info, update the {@link #currentFragment} and the Transaction data.
292+
* Parse the intent set by {@link TransactionData#deepLinkIntent} and if this intent contains
293+
* Deep Link info, update the {@link #currentFragment} and the Transaction data.
281294
*
282-
* @param data The Transaction data to extend if Deep link info are found in the {@link TransactionData#deepLinkIntent}.
295+
* @param data The Transaction data to extend if Deep link info are found in
296+
* the {@link TransactionData#deepLinkIntent}.
283297
* @param <T> The generic type for a valid Fragment.
284298
*/
285299
@SuppressWarnings("unchecked")
286300
private <T extends Fragment & FlowrFragment> void injectDeepLinkInfo(TransactionData<T> data) {
287301
Intent deepLinkIntent = data.getDeepLinkIntent();
288302
if (deepLinkIntent != null) {
289-
deepLinkHandler = getDeepLinkHandler();
290-
if (deepLinkHandler != null) {
291-
FlowrDeepLinkInfo info = deepLinkHandler.routeIntentToScreen(deepLinkIntent);
303+
for (FlowrDeepLinkHandler handler : deepLinkHandlers) {
304+
FlowrDeepLinkInfo info = handler.getDeepLinkInfoForIntent(deepLinkIntent);
305+
292306
if (info != null) {
293307
data.setFragmentClass(info.fragment);
294308
Bundle dataArgs = data.getArgs();
@@ -297,6 +311,8 @@ private <T extends Fragment & FlowrFragment> void injectDeepLinkInfo(Transaction
297311
} else {
298312
data.setArgs(info.data);
299313
}
314+
315+
break;
300316
}
301317
}
302318
}
@@ -604,25 +620,6 @@ public void onDestroy() {
604620
setDrawerHandler(null);
605621
}
606622

607-
/**
608-
* return the {@link FlowrDeepLinkHandler} if already initialized or initialize one.
609-
*
610-
* @return Initialized {@link FlowrDeepLinkHandler}
611-
*/
612-
public FlowrDeepLinkHandler getDeepLinkHandler() {
613-
try {
614-
if (deepLinkHandler == null) {
615-
Constructor<? extends FlowrDeepLinkHandler> deepLinkCtor = findBindingConstructorForClass();
616-
if (deepLinkCtor != null) {
617-
deepLinkHandler = deepLinkCtor.newInstance();
618-
}
619-
}
620-
} catch (Exception e) {
621-
Log.e(TAG, "Error retrieving DeeplinkHandler", e);
622-
}
623-
return deepLinkHandler;
624-
}
625-
626623
/**
627624
* This builder class is used to show a new fragment inside the current activity
628625
*/
@@ -691,7 +688,8 @@ public Builder replaceCurrentFragment(boolean replaceCurrentFragment) {
691688
* @param exitAnim the fragment exit animation.
692689
*/
693690
public Builder setCustomTransactionAnimation(@AnimRes int enterAnim, @AnimRes int exitAnim) {
694-
return setCustomTransactionAnimation(enterAnim, FragmentTransaction.TRANSIT_NONE, FragmentTransaction.TRANSIT_NONE, exitAnim);
691+
return setCustomTransactionAnimation(enterAnim, FragmentTransaction.TRANSIT_NONE,
692+
FragmentTransaction.TRANSIT_NONE, exitAnim);
695693
}
696694

697695

@@ -703,8 +701,8 @@ public Builder setCustomTransactionAnimation(@AnimRes int enterAnim, @AnimRes in
703701
* @param popEnterAnim The animation resource to be used when the previous fragment enters on back pressed.
704702
* @param popExitAnim The animation resource to be used when the current fragment exits on back pressed..
705703
*/
706-
public Builder setCustomTransactionAnimation(@AnimRes int enterAnim,
707-
@AnimRes int exitAnim, @AnimRes int popEnterAnim, @AnimRes int popExitAnim) {
704+
public Builder setCustomTransactionAnimation(@AnimRes int enterAnim, @AnimRes int exitAnim,
705+
@AnimRes int popEnterAnim, @AnimRes int popExitAnim) {
708706
data.setEnterAnim(enterAnim);
709707
data.setExitAnim(exitAnim);
710708
data.setPopEnterAnim(popEnterAnim);
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.fueled.flowr.internal;
2+
3+
import android.content.Intent;
4+
import android.net.Uri;
5+
import android.os.Bundle;
6+
import android.support.annotation.NonNull;
7+
import android.support.annotation.Nullable;
8+
import android.support.v4.app.Fragment;
9+
10+
import com.fueled.flowr.Flowr;
11+
import com.fueled.flowr.FlowrFragment;
12+
13+
import java.util.ArrayList;
14+
import java.util.HashMap;
15+
import java.util.Iterator;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.regex.Matcher;
19+
import java.util.regex.Pattern;
20+
21+
/**
22+
* Created by [email protected] on 04/06/2017.
23+
* Copyright (c) 2017 Hussein Ala. All rights reserved.
24+
*/
25+
public class AbstractFlowrDeepLinkHandler<T extends Fragment & FlowrFragment>
26+
implements FlowrDeepLinkHandler {
27+
28+
private static final String NAMED_PARAM_REGEX = "\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>";
29+
private static final Pattern NAMED_PARAM_PATTERN = Pattern.compile(NAMED_PARAM_REGEX);
30+
31+
private final Map<String, Class<? extends T>> linkFragmentMap;
32+
33+
public AbstractFlowrDeepLinkHandler() {
34+
linkFragmentMap = new HashMap<>();
35+
}
36+
37+
/**
38+
* Add a new link between a specific URL pattern and a specific fragment class.
39+
*
40+
* @param url the URL pattern to link the fragment class to.
41+
* @param fragment the fragment class to be linked to the URL pattern.
42+
*/
43+
protected void addFragment(String url, Class<? extends T> fragment) {
44+
linkFragmentMap.put(url, fragment);
45+
}
46+
47+
/**
48+
* Generates a bundle of the url named path variables and parameters.
49+
*
50+
* @param uri the url to generate the bundle from.
51+
* @param pattern the pattern to extract named path variables with.
52+
* @return a bundle containing all path variables and parameters as strings.
53+
*/
54+
@Nullable
55+
private static Bundle bundleUriInfo(Uri uri, String pattern) {
56+
String regex = getRegexPattern(pattern);
57+
String path = uri.getPath();
58+
Matcher m = Pattern.compile(regex).matcher(path);
59+
60+
if (m.matches()) {
61+
Bundle data = new Bundle();
62+
data.putString(Flowr.DEEP_LINK_URL, uri.toString());
63+
Iterator<String> params = getNamedGroupCandidates(regex).iterator();
64+
65+
//start at 1 because 0 is the searched string
66+
int i = 1;
67+
while (params.hasNext()) {
68+
String bip = params.next();
69+
data.putString(bip, m.group(i));
70+
i++;
71+
}
72+
73+
return data;
74+
}
75+
76+
return null;
77+
}
78+
79+
private static String getRegexPattern(String pattern) {
80+
return pattern.replaceAll("\\{(.+?)\\}", "(?<$1>\\.+?)");
81+
}
82+
83+
private static List<String> getNamedGroupCandidates(String regex) {
84+
List<String> namedGroups = new ArrayList<>();
85+
86+
Matcher m = NAMED_PARAM_PATTERN.matcher(regex);
87+
88+
while (m.find()) {
89+
String test = m.group(1);
90+
namedGroups.add(test);
91+
}
92+
93+
return namedGroups;
94+
}
95+
96+
/**
97+
* @inheritDoc
98+
*/
99+
@Nullable
100+
public FlowrDeepLinkInfo getDeepLinkInfoForIntent(@NonNull Intent intent) {
101+
Uri uri = intent.getData();
102+
103+
if (uri != null) {
104+
for (String uriPattern : linkFragmentMap.keySet()) {
105+
Bundle data = bundleUriInfo(uri, uriPattern);
106+
if (data != null) {
107+
return new FlowrDeepLinkInfo<>(data, linkFragmentMap.get(uriPattern));
108+
}
109+
}
110+
}
111+
112+
return null;
113+
}
114+
115+
}

0 commit comments

Comments
 (0)