Skip to content

[cronet_http] Upgrade jni and jnigen to 0.14.2 #1793

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkgs/cronet_http/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Add a new `CronetStreamedResponse` class that provides additional information
about the HTTP response.
* Fix a Flutter warning by upgrading to Kotlin 1.18.10.
* Upgrade `package:jni` and `package:jnigen` to 0.14.2.

## 1.3.4

Expand Down
6 changes: 3 additions & 3 deletions pkgs/cronet_http/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ android {
compileSdkVersion 31

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '11'
}

sourceSets {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,58 +19,69 @@ import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo
import java.nio.ByteBuffer

// Due to a bug (https://github.com/dart-lang/native/issues/2421) where JNIgen
// does not synchronize the nullabilities across the class hierarchy and the
// fact that UrlRequest.Callback is a Java class with no nullability
// annotations, generating both `UrlRequestCallbackProxy` and
// `UrlRequest.Callback` together with different nullabilities causes the
// super method to have a looser type for parameters which is a Dart compilation
// error.
// That is why all of the parameters of this class are defined as nullable to
// match `UrlRequest.Callback` while in reality only `onFailed`'s `info`
// parameter is nullable as specified in the cronet source code:
// https://source.chromium.org/chromium/chromium/src/+/main:components/cronet/android/api/src/org/chromium/net/UrlRequest.java;l=232

class UrlRequestCallbackProxy(val callback: UrlRequestCallbackInterface) : UrlRequest.Callback() {
public interface UrlRequestCallbackInterface {
fun onRedirectReceived(
request: UrlRequest,
info: UrlResponseInfo,
newLocationUrl: String
request: UrlRequest?,
info: UrlResponseInfo?,
newLocationUrl: String?
)

fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo)
fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?)
fun onReadCompleted(
request: UrlRequest,
info: UrlResponseInfo,
byteBuffer: ByteBuffer
request: UrlRequest?,
info: UrlResponseInfo?,
byteBuffer: ByteBuffer?
)

fun onSucceeded(request: UrlRequest, info: UrlResponseInfo?)
fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?)
fun onFailed(
request: UrlRequest,
request: UrlRequest?,
info: UrlResponseInfo?,
error: CronetException
error: CronetException?
)
}

override fun onRedirectReceived(
request: UrlRequest,
info: UrlResponseInfo,
newLocationUrl: String
request: UrlRequest?,
info: UrlResponseInfo?,
newLocationUrl: String?
) {
callback.onRedirectReceived(request, info, newLocationUrl);
}

override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo) {
override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) {
callback.onResponseStarted(request, info);
}

override fun onReadCompleted(
request: UrlRequest,
info: UrlResponseInfo,
byteBuffer: ByteBuffer
request: UrlRequest?,
info: UrlResponseInfo?,
byteBuffer: ByteBuffer?
) {
callback.onReadCompleted(request, info, byteBuffer);
}

override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo?) {
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
callback.onSucceeded(request, info);
}

override fun onFailed(
request: UrlRequest,
request: UrlRequest?,
info: UrlResponseInfo?,
error: CronetException
error: CronetException?
) {
callback.onFailed(request, info, error);
}
Expand Down
9 changes: 3 additions & 6 deletions pkgs/cronet_http/example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ android {
namespace 'io.flutter.cronet_http_example'

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '11'
}

sourceSets {
Expand Down Expand Up @@ -66,9 +66,6 @@ flutter {
}

dependencies {
// TODO(#1112): org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions.
// This should be removed when https://github.com/flutter/flutter/issues/125062 is fixed.
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
// ""com.google.android.gms:play-services-cronet" is only present so that
// `jnigen` will work. Applications should not include this line.
// The version should be synced with `pkgs/cronet_http/android/build.gradle`.
Expand Down
8 changes: 8 additions & 0 deletions pkgs/cronet_http/example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
allprojects {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}

rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
Expand Down
70 changes: 38 additions & 32 deletions pkgs/cronet_http/lib/src/cronet_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ class CronetStreamedResponse extends _StreamedResponseWithUrl {
///
/// It will be the empty string or `'unknown'` if no protocol was negotiated,
/// the protocol is not known, or when using plain HTTP or HTTPS.
String get negotiatedProtocol =>
_responseInfo.getNegotiatedProtocol().toDartString(releaseOriginal: true);
String get negotiatedProtocol => _responseInfo
.getNegotiatedProtocol()!
.toDartString(releaseOriginal: true);

/// The minimum count of bytes received from the network to process this
/// request.
Expand Down Expand Up @@ -124,7 +125,7 @@ class CronetEngine {
bool? enableQuic,
String? storagePath,
String? userAgent}) {
final builder = jb.CronetEngine_Builder(
final builder = jb.CronetEngine$Builder(
JObject.fromReference(Jni.getCachedApplicationContext()));

try {
Expand Down Expand Up @@ -159,7 +160,7 @@ class CronetEngine {
builder.setUserAgent(userAgent.toJString());
}

return CronetEngine._(builder.build());
return CronetEngine._(builder.build()!);
} on JniException catch (e) {
// TODO: Decode this exception in a better way when
// https://github.com/dart-lang/jnigen/issues/239 is fixed.
Expand All @@ -182,12 +183,12 @@ class CronetEngine {
}

Map<String, String> _cronetToClientHeaders(
JMap<JString, JList<JString>> cronetHeaders) =>
JMap<JString?, JList<JString?>?> cronetHeaders) =>
cronetHeaders.map((key, value) => MapEntry(
key.toDartString(releaseOriginal: true).toLowerCase(),
value.join(',')));
key!.toDartString(releaseOriginal: true).toLowerCase(),
value!.join(',')));

jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
jb.UrlRequestCallbackProxy$UrlRequestCallbackInterface _urlRequestCallbacks(
BaseRequest request,
Completer<CronetStreamedResponse> responseCompleter,
HttpClientRequestProfile? profile) {
Expand All @@ -198,21 +199,24 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(

// The order of callbacks generated by Cronet is documented here:
// https://developer.android.com/guide/topics/connectivity/cronet/lifecycle
return jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface.implement(
jb.$UrlRequestCallbackProxy_UrlRequestCallbackInterface(
return jb.UrlRequestCallbackProxy$UrlRequestCallbackInterface.implement(
// All of the variables in the interface are non-nullable with the
// exception of onFailed's UrlResponseInfo as specified in:
// https://source.chromium.org/chromium/chromium/src/+/main:components/cronet/android/api/src/org/chromium/net/UrlRequest.java;l=232
jb.$UrlRequestCallbackProxy$UrlRequestCallbackInterface(
onResponseStarted: (urlRequest, responseInfo) {
responseStream = StreamController(onCancel: () {
// The user did `response.stream.cancel()`. We can just pretend that
// the response completed normally.
if (done) return;
done = true;
urlRequest.cancel();
urlRequest!.cancel();
responseStream!.sink.close();
jByteBuffer?.release();
profile?.responseData.close();
});
final responseHeaders =
_cronetToClientHeaders(responseInfo.getAllHeaders());
_cronetToClientHeaders(responseInfo!.getAllHeaders()!);
int? contentLength;

switch (responseHeaders['content-length']) {
Expand All @@ -222,7 +226,7 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
'Invalid content-length header [$contentLengthHeader].',
request.url,
));
urlRequest.cancel();
urlRequest?.cancel();
return;
case final contentLengthHeader?:
contentLength = int.parse(contentLengthHeader);
Expand All @@ -232,10 +236,10 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
responseInfo.getHttpStatusCode(),
responseInfo: responseInfo,
url: Uri.parse(
responseInfo.getUrl().toDartString(releaseOriginal: true)),
responseInfo.getUrl()!.toDartString(releaseOriginal: true)),
contentLength: contentLength,
reasonPhrase: responseInfo
.getHttpStatusText()
.getHttpStatusText()!
.toDartString(releaseOriginal: true),
request: request,
isRedirect: false,
Expand All @@ -247,39 +251,40 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
?..contentLength = contentLength
..headersCommaValues = responseHeaders
..isRedirect = false
..reasonPhrase =
responseInfo.getHttpStatusText().toDartString(releaseOriginal: true)
..reasonPhrase = responseInfo
.getHttpStatusText()!
.toDartString(releaseOriginal: true)
..startTime = DateTime.now()
..statusCode = responseInfo.getHttpStatusCode();
jByteBuffer = JByteBuffer.allocateDirect(_bufferSize);
urlRequest.read(jByteBuffer!);
urlRequest?.read(jByteBuffer!);
},
onRedirectReceived: (urlRequest, responseInfo, newLocationUrl) {
if (done) return;
final responseHeaders =
_cronetToClientHeaders(responseInfo.getAllHeaders());
_cronetToClientHeaders(responseInfo!.getAllHeaders()!);

if (!request.followRedirects) {
urlRequest.cancel();
urlRequest!.cancel();
responseCompleter.complete(CronetStreamedResponse._(
const Stream.empty(), // Cronet provides no body for redirects.
responseInfo.getHttpStatusCode(),
responseInfo: responseInfo,
url: Uri.parse(
responseInfo.getUrl().toDartString(releaseOriginal: true)),
responseInfo.getUrl()!.toDartString(releaseOriginal: true)),
contentLength: 0,
reasonPhrase: responseInfo
.getHttpStatusText()
.getHttpStatusText()!
.toDartString(releaseOriginal: true),
request: request,
isRedirect: true,
headers: _cronetToClientHeaders(responseInfo.getAllHeaders())));
headers: _cronetToClientHeaders(responseInfo.getAllHeaders()!)));

profile?.responseData
?..headersCommaValues = responseHeaders
..isRedirect = true
..reasonPhrase = responseInfo
.getHttpStatusText()
.getHttpStatusText()!
.toDartString(releaseOriginal: true)
..startTime = DateTime.now()
..statusCode = responseInfo.getHttpStatusCode();
Expand All @@ -294,23 +299,23 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
// does not seem to have a way to get the method so we'd have to
// calculate it according to the rules in RFC-7231.
method: 'GET',
location: newLocationUrl.toDartString(releaseOriginal: true)));
urlRequest.followRedirect();
location: newLocationUrl!.toDartString(releaseOriginal: true)));
urlRequest!.followRedirect();
} else {
urlRequest.cancel();
urlRequest!.cancel();
responseCompleter.completeError(
ClientException('Redirect limit exceeded', request.url));
}
},
onReadCompleted: (urlRequest, responseInfo, byteBuffer) {
if (done) return;
byteBuffer.flip();
byteBuffer!.flip();
final data = jByteBuffer!.asUint8List().sublist(0, byteBuffer.remaining);
responseStream!.add(data);
profile?.responseData.bodySink.add(data);

byteBuffer.clear();
urlRequest.read(byteBuffer);
urlRequest!.read(byteBuffer);
},
onSucceeded: (urlRequest, responseInfo) {
if (done) return;
Expand All @@ -319,7 +324,7 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
jByteBuffer?.release();
profile?.responseData.close();
},
onFailed: (urlRequest, responseInfo, cronetException) {
onFailed: (urlRequest, responseInfo /* can be null */, cronetException) {
if (done) return;
done = true;
final error = ClientException(
Expand Down Expand Up @@ -442,7 +447,8 @@ class CronetClient extends BaseClient {
jb.UrlRequestCallbackProxy(
_urlRequestCallbacks(request, responseCompleter, profile)),
_executor,
)..setHttpMethod(request.method.toJString());
)!
..setHttpMethod(request.method.toJString());

var headers = request.headers;
if (body.isNotEmpty &&
Expand Down Expand Up @@ -471,7 +477,7 @@ class CronetClient extends BaseClient {
builder.setUploadDataProvider(
jb.UploadDataProviders.create$2(data), _executor);
}
builder.build().start();
builder.build()!.start();
return responseCompleter.future;
}
}
Expand Down
Loading
Loading