Skip to content

Commit 8d6b65f

Browse files
authored
Merge pull request #108 from contentstack/feat/DX-7327-variants-branch-support
Add support for variant branches in Entry and Query classes
2 parents 7985538 + 54cc0f8 commit 8d6b65f

8 files changed

Lines changed: 367 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# CHANGELOG
22

3+
## Version 4.4.0
4+
5+
### Date: 20-Jul-2026
6+
7+
### Feature
8+
9+
- Branch support in entry variants
10+
311
## Version 4.3.0
412

513
### Date: 29-Jun-2026

contentstack/build.gradle

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ plugins {
77
ext {
88
PUBLISH_GROUP_ID = 'com.contentstack.sdk'
99
PUBLISH_ARTIFACT_ID = 'android'
10-
PUBLISH_VERSION = '4.3.0'
10+
PUBLISH_VERSION = '4.4.0'
1111
}
1212

1313
android {
@@ -108,6 +108,7 @@ android {
108108
buildConfigField "String", "variantUID", getPropertyOrEmpty('variantUID')
109109
buildConfigField "String", "variantEntryUID", getPropertyOrEmpty('variantEntryUID')
110110
buildConfigField "String[]", "variantsUID", variantsAsArrayString
111+
buildConfigField "String", "variantBranch", getPropertyOrEmpty('variantBranch')
111112
}
112113
release {
113114
minifyEnabled false
@@ -144,6 +145,10 @@ dependencies {
144145
}
145146
}
146147
}
148+
// androidx.test/espresso pull bcprov-jdk18on 1.80 (test-only); force the patched
149+
// release for Snyk SNYK-JAVA-ORGBOUNCYCASTLE-16074612 (CVE-2026-5598, Timing Attack)
150+
// and SNYK-JAVA-ORGBOUNCYCASTLE-16075266 (CVE-2025-14813). Not shipped in the AAR.
151+
configurations.configureEach { resolutionStrategy.force 'org.bouncycastle:bcprov-jdk18on:1.85' }
147152
implementation fileTree(include: ['*.jar'], dir: 'libs')
148153
implementation "com.android.volley:volley:$volley"
149154

contentstack/src/androidTest/java/com/contentstack/sdk/EntryTestCase.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class EntryTestCase {
3131
private static String variantUID = BuildConfig.variantUID;
3232
private static String variantEntryUID = BuildConfig.variantEntryUID;
3333
private static String[] variantsUID = BuildConfig.variantsUID;
34+
private static String variantBranch = BuildConfig.variantBranch;
3435

3536

3637
@BeforeClass
@@ -373,4 +374,44 @@ public void onCompletion(ResponseType responseType, Error error) {
373374
latch.await();
374375
}
375376

377+
@Test
378+
public void VariantsTestSingleUidWithBranch() throws InterruptedException {
379+
Assume.assumeFalse("variantEntryUID not configured", variantEntryUID == null || variantEntryUID.trim().isEmpty());
380+
Assume.assumeFalse("variantUID not configured", variantUID == null || variantUID.trim().isEmpty());
381+
Assume.assumeFalse("variantBranch not configured", variantBranch == null || variantBranch.trim().isEmpty());
382+
final CountDownLatch latch = new CountDownLatch(1);
383+
final Entry entry = stack.contentType("product").entry(variantEntryUID).variants(variantUID, variantBranch);
384+
assertEquals(variantUID.trim(), entry.getHeaders().get("x-cs-variant-uid"));
385+
assertEquals(variantBranch.trim(), entry.getHeaders().get("branch"));
386+
387+
entry.fetch(new EntryResultCallBack() {
388+
@Override
389+
public void onCompletion(ResponseType responseType, Error error) {
390+
System.out.println(entry.toJSON());
391+
latch.countDown();
392+
}
393+
});
394+
assertTrue("fetch() callback timed out", latch.await(30, TimeUnit.SECONDS));
395+
}
396+
397+
@Test
398+
public void VariantsTestArrayWithBranch() throws InterruptedException {
399+
Assume.assumeFalse("variantEntryUID not configured", variantEntryUID == null || variantEntryUID.trim().isEmpty());
400+
Assume.assumeFalse("variantsUID not configured", variantsUID == null || variantsUID.length == 0);
401+
Assume.assumeFalse("variantBranch not configured", variantBranch == null || variantBranch.trim().isEmpty());
402+
final CountDownLatch latch = new CountDownLatch(1);
403+
final Entry entry = stack.contentType("product").entry(variantEntryUID).variants(variantsUID, variantBranch);
404+
assertNotNull(entry.getHeaders().get("x-cs-variant-uid"));
405+
assertEquals(variantBranch.trim(), entry.getHeaders().get("branch"));
406+
407+
entry.fetch(new EntryResultCallBack() {
408+
@Override
409+
public void onCompletion(ResponseType responseType, Error error) {
410+
System.out.println(entry.toJSON());
411+
latch.countDown();
412+
}
413+
});
414+
assertTrue("fetch() callback timed out", latch.await(30, TimeUnit.SECONDS));
415+
}
416+
376417
}

contentstack/src/androidTest/java/com/contentstack/sdk/QueryTestCase.java

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@
99
import java.util.ArrayList;
1010
import java.util.Iterator;
1111
import java.util.List;
12+
import java.util.concurrent.CountDownLatch;
13+
import java.util.concurrent.TimeUnit;
1214

13-
import static junit.framework.TestCase.assertEquals;
15+
import static junit.framework.TestCase.*;
1416

1517

1618
public class QueryTestCase {
1719

1820
private static final String TAG = AssetTestCase.class.getSimpleName();
1921
private static final String contentTypeUID = BuildConfig.contentTypeUID;
22+
private static final String variantUID = BuildConfig.variantUID;
23+
private static final String[] variantsUID = BuildConfig.variantsUID;
24+
private static final String variantBranch = BuildConfig.variantBranch;
2025
private static Query query;
2126

2227
static {
@@ -116,4 +121,90 @@ public void onCompletion(ResponseType responseType, QueryResult queryresult, Err
116121
});
117122
}
118123

124+
@Test
125+
public void test_42_variants_single_uid_find() throws Exception {
126+
Assume.assumeFalse("variantUID not configured", variantUID == null || variantUID.trim().isEmpty());
127+
final Query q = TestCred.stack().contentType(contentTypeUID).query();
128+
q.variants(variantUID);
129+
assertEquals(variantUID.trim(), q.getHeaders().get("x-cs-variant-uid"));
130+
assertNull(q.getHeaders().get("branch"));
131+
132+
final CountDownLatch latch = new CountDownLatch(1);
133+
q.find(new QueryResultsCallBack() {
134+
@Override
135+
public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
136+
if (error == null) {
137+
Log.d(TAG, "variants single find: " + queryresult.getResultObjects().size() + " entries");
138+
}
139+
latch.countDown();
140+
}
141+
});
142+
assertTrue("find() callback timed out", latch.await(30, TimeUnit.SECONDS));
143+
}
144+
145+
@Test
146+
public void test_43_variants_array_find() throws Exception {
147+
Assume.assumeFalse("variantsUID not configured", variantsUID == null || variantsUID.length == 0);
148+
final Query q = TestCred.stack().contentType(contentTypeUID).query();
149+
q.variants(variantsUID);
150+
assertNotNull(q.getHeaders().get("x-cs-variant-uid"));
151+
assertNull(q.getHeaders().get("branch"));
152+
153+
final CountDownLatch latch = new CountDownLatch(1);
154+
q.find(new QueryResultsCallBack() {
155+
@Override
156+
public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
157+
if (error == null) {
158+
Log.d(TAG, "variants array find: " + queryresult.getResultObjects().size() + " entries");
159+
}
160+
latch.countDown();
161+
}
162+
});
163+
assertTrue("find() callback timed out", latch.await(30, TimeUnit.SECONDS));
164+
}
165+
166+
@Test
167+
public void test_44_variants_single_uid_with_branch_find() throws Exception {
168+
Assume.assumeFalse("variantUID not configured", variantUID == null || variantUID.trim().isEmpty());
169+
Assume.assumeFalse("variantBranch not configured", variantBranch == null || variantBranch.trim().isEmpty());
170+
final Query q = TestCred.stack().contentType(contentTypeUID).query();
171+
q.variants(variantUID, variantBranch);
172+
assertEquals(variantUID.trim(), q.getHeaders().get("x-cs-variant-uid"));
173+
assertEquals(variantBranch.trim(), q.getHeaders().get("branch"));
174+
175+
final CountDownLatch latch = new CountDownLatch(1);
176+
q.find(new QueryResultsCallBack() {
177+
@Override
178+
public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
179+
if (error == null) {
180+
Log.d(TAG, "variants single+branch find: " + queryresult.getResultObjects().size() + " entries");
181+
}
182+
latch.countDown();
183+
}
184+
});
185+
assertTrue("find() callback timed out", latch.await(30, TimeUnit.SECONDS));
186+
}
187+
188+
@Test
189+
public void test_45_variants_array_with_branch_find() throws Exception {
190+
Assume.assumeFalse("variantsUID not configured", variantsUID == null || variantsUID.length == 0);
191+
Assume.assumeFalse("variantBranch not configured", variantBranch == null || variantBranch.trim().isEmpty());
192+
final Query q = TestCred.stack().contentType(contentTypeUID).query();
193+
q.variants(variantsUID, variantBranch);
194+
assertNotNull(q.getHeaders().get("x-cs-variant-uid"));
195+
assertEquals(variantBranch.trim(), q.getHeaders().get("branch"));
196+
197+
final CountDownLatch latch = new CountDownLatch(1);
198+
q.find(new QueryResultsCallBack() {
199+
@Override
200+
public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
201+
if (error == null) {
202+
Log.d(TAG, "variants array+branch find: " + queryresult.getResultObjects().size() + " entries");
203+
}
204+
latch.countDown();
205+
}
206+
});
207+
assertTrue("find() callback timed out", latch.await(30, TimeUnit.SECONDS));
208+
}
209+
119210
}

contentstack/src/main/java/com/contentstack/sdk/Entry.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1532,7 +1532,7 @@ public Entry includeMetadata() {
15321532
* stack.contentType("user").entry("entry_uid").variant("variant_uid").fetch();
15331533
*/
15341534
public Entry variants(String variants){
1535-
if (variants != null && variants.length() > 0) {
1535+
if (variants != null && !variants.trim().isEmpty()) {
15361536
this.localHeader.put("x-cs-variant-uid", variants.trim());
15371537
}
15381538
return this;
@@ -1551,6 +1551,38 @@ public Entry variants(String[] variants){
15511551
}
15521552
return this;
15531553
}
1554+
1555+
/**
1556+
* Sets the variant UID and scopes the request to the given branch.
1557+
* Sets {@code x-cs-variant-uid} and {@code branch} request headers.
1558+
*
1559+
* @param variantUid single variant UID
1560+
* @param branch branch name to scope the request
1561+
* @return {@link Entry}
1562+
*/
1563+
public Entry variants(String variantUid, String branch) {
1564+
variants(variantUid);
1565+
if (branch != null && !branch.trim().isEmpty()) {
1566+
this.localHeader.put("branch", branch.trim());
1567+
}
1568+
return this;
1569+
}
1570+
1571+
/**
1572+
* Sets multiple variant UIDs and scopes the request to the given branch.
1573+
* Sets {@code x-cs-variant-uid} and {@code branch} request headers.
1574+
*
1575+
* @param variantUids array of variant UIDs
1576+
* @param branch branch name to scope the request
1577+
* @return {@link Entry}
1578+
*/
1579+
public Entry variants(String[] variantUids, String branch) {
1580+
variants(variantUids);
1581+
if (branch != null && !branch.trim().isEmpty()) {
1582+
this.localHeader.put("branch", branch.trim());
1583+
}
1584+
return this;
1585+
}
15541586
public ArrayMap<String, Object> getHeaders() {
15551587
return localHeader;
15561588
}

contentstack/src/main/java/com/contentstack/sdk/Query.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,5 +2025,75 @@ public Query includeMetadata() {
20252025
return this;
20262026
}
20272027

2028+
/**
2029+
* Sets the variant UID for this query via the {@code x-cs-variant-uid} request header.
2030+
*
2031+
* @param variantUid single variant UID
2032+
* @return {@link Query}
2033+
*/
2034+
public Query variants(String variantUid) {
2035+
if (variantUid != null && !variantUid.trim().isEmpty()) {
2036+
localHeader.put("x-cs-variant-uid", variantUid.trim());
2037+
}
2038+
return this;
2039+
}
2040+
2041+
/**
2042+
* Sets multiple variant UIDs for this query via the {@code x-cs-variant-uid} request header.
2043+
*
2044+
* @param variantUids array of variant UIDs
2045+
* @return {@link Query}
2046+
*/
2047+
public Query variants(String[] variantUids) {
2048+
if (variantUids != null && variantUids.length > 0) {
2049+
List<String> variantList = new ArrayList<>();
2050+
for (String variant : variantUids) {
2051+
if (variant != null && !variant.trim().isEmpty()) {
2052+
variantList.add(variant.trim());
2053+
}
2054+
}
2055+
if (!variantList.isEmpty()) {
2056+
localHeader.put("x-cs-variant-uid", String.join(", ", variantList));
2057+
}
2058+
}
2059+
return this;
2060+
}
2061+
2062+
/**
2063+
* Sets the variant UID and scopes the query to the given branch.
2064+
* Sets {@code x-cs-variant-uid} and {@code branch} request headers.
2065+
*
2066+
* @param variantUid single variant UID
2067+
* @param branch branch name to scope the request
2068+
* @return {@link Query}
2069+
*/
2070+
public Query variants(String variantUid, String branch) {
2071+
variants(variantUid);
2072+
if (branch != null && !branch.trim().isEmpty()) {
2073+
localHeader.put("branch", branch.trim());
2074+
}
2075+
return this;
2076+
}
2077+
2078+
/**
2079+
* Sets multiple variant UIDs and scopes the query to the given branch.
2080+
* Sets {@code x-cs-variant-uid} and {@code branch} request headers.
2081+
*
2082+
* @param variantUids array of variant UIDs
2083+
* @param branch branch name to scope the request
2084+
* @return {@link Query}
2085+
*/
2086+
public Query variants(String[] variantUids, String branch) {
2087+
variants(variantUids);
2088+
if (branch != null && !branch.trim().isEmpty()) {
2089+
localHeader.put("branch", branch.trim());
2090+
}
2091+
return this;
2092+
}
2093+
2094+
public ArrayMap<String, Object> getHeaders() {
2095+
return localHeader;
2096+
}
2097+
20282098
}
20292099

contentstack/src/test/java/com/contentstack/sdk/TestEntryComprehensive.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,50 @@ public void testVariantsArrayNull() {
353353
assertNotNull(result);
354354
}
355355

356+
@Test
357+
public void testVariantsSingleWithBranch() {
358+
Entry result = entry.variants("xyz", "branch_name");
359+
assertNotNull(result);
360+
assertEquals("xyz", entry.getHeaders().get("x-cs-variant-uid"));
361+
assertEquals("branch_name", entry.getHeaders().get("branch"));
362+
}
363+
364+
@Test
365+
public void testVariantsArrayWithBranch() {
366+
Entry result = entry.variants(new String[]{"variant1", "variant2"}, "branch_name");
367+
assertNotNull(result);
368+
assertEquals("variant1, variant2", entry.getHeaders().get("x-cs-variant-uid"));
369+
assertEquals("branch_name", entry.getHeaders().get("branch"));
370+
}
371+
372+
@Test
373+
public void testVariantsArrayWithBranchFiltersNullsAndBlanks() {
374+
entry.variants(new String[]{"v1", null, " ", "v2"}, "staging");
375+
assertEquals("v1, v2", entry.getHeaders().get("x-cs-variant-uid"));
376+
assertEquals("staging", entry.getHeaders().get("branch"));
377+
}
378+
379+
@Test
380+
public void testVariantsSingleNullBranchDoesNotSetBranchHeader() {
381+
entry.variants("xyz", null);
382+
assertEquals("xyz", entry.getHeaders().get("x-cs-variant-uid"));
383+
assertNull(entry.getHeaders().get("branch"));
384+
}
385+
386+
@Test
387+
public void testVariantsArrayNullBranchDoesNotSetBranchHeader() {
388+
entry.variants(new String[]{"v1", "v2"}, null);
389+
assertEquals("v1, v2", entry.getHeaders().get("x-cs-variant-uid"));
390+
assertNull(entry.getHeaders().get("branch"));
391+
}
392+
393+
@Test
394+
public void testVariantsSingleEmptyBranchDoesNotSetBranchHeader() {
395+
entry.variants("xyz", " ");
396+
assertEquals("xyz", entry.getHeaders().get("x-cs-variant-uid"));
397+
assertNull(entry.getHeaders().get("branch"));
398+
}
399+
356400
// ==================== Locale ====================
357401

358402
@Test

0 commit comments

Comments
 (0)