-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Closed
Labels
a: existing-appsIntegration with existing apps via the add-to-app flowIntegration with existing apps via the add-to-app flowengineflutter/engine related. See also e: labels.flutter/engine related. See also e: labels.found in release: 3.16Found to occur in 3.16Found to occur in 3.16found in release: 3.19Found to occur in 3.19Found to occur in 3.19frameworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onplatform-androidAndroid applications specificallyAndroid applications specificallyr: fixedIssue is closed as already fixed in a newer versionIssue is closed as already fixed in a newer versionteam-androidOwned by Android platform teamOwned by Android platform team
Description
Steps to reproduce
I am using the official demo case, https://flutter.cn/docs/development/add-to-app/android/add-flutter-view, and copy the code to run locally.
When RecyclerView recycles items, the Run window will prompt an exception, but this exception does not affect normal use, at least for now.
Expected results
...
Actual results
...
Code sample
Flutter Code
cell.dart
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sensors/sensors.dart';
// This is on alternate entrypoint for this module to display Flutter UI in
// a (multi-)view integration scenario.
void main() {
runApp(const Cell());
}
class Cell extends StatefulWidget {
const Cell({super.key});
@override
State<StatefulWidget> createState() => _CellState();
}
class _CellState extends State<Cell> with WidgetsBindingObserver {
static const double gravity = 9.81;
static final AccelerometerEvent defaultPosition = AccelerometerEvent(0, 0, 0);
int cellNumber = 0;
Random? _random;
AppLifecycleState? appLifecycleState;
@override
void initState() {
const channel = MethodChannel('dev.flutter.example/cell');
channel.setMethodCallHandler((call) async {
if (call.method == 'setCellNumber') {
setState(() {
cellNumber = call.arguments as int;
_random = Random(cellNumber);
});
}
});
// Keep track of what the current platform lifecycle state is.
WidgetsBinding.instance.addObserver(this);
super.initState();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
appLifecycleState = state;
});
}
// Show a random bright color.
Color randomLightColor() {
_random ??= Random(cellNumber);
return Color.fromARGB(255, _random!.nextInt(50) + 205,
_random!.nextInt(50) + 205, _random!.nextInt(50) + 205);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
// The Flutter cells will be noticeably different (due to background color
// and the Flutter logo). The banner breaks immersion.
debugShowCheckedModeBanner: false,
home: Container(
color: Colors.white,
child: Builder(
builder: (context) {
return Card(
// Mimic the platform Material look.
margin: const EdgeInsets.symmetric(horizontal: 36, vertical: 24),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 16,
color: randomLightColor(),
child: Stack(
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
// Show a number provided by the platform based on
// the cell's index.
cellNumber.toString(),
style: Theme.of(context).textTheme.displaySmall,
),
],
),
),
Positioned(
left: 42,
top: 0,
bottom: 0,
child: Opacity(
opacity: 0.2,
child: StreamBuilder<AccelerometerEvent>(
// Don't continuously rebuild for nothing when the
// cell isn't visible.
stream: appLifecycleState == AppLifecycleState.resumed
? accelerometerEvents
: Stream.value(defaultPosition),
initialData: defaultPosition,
builder: (context, snapshot) {
return Transform(
// Figure out the phone's orientation relative
// to gravity's direction. Ignore the z vector.
transform: Matrix4.rotationX(
snapshot.data!.y / gravity * pi / 2)
..multiply(Matrix4.rotationY(
snapshot.data!.x / gravity * pi / 2)),
alignment: Alignment.center,
child: const FlutterLogo(size: 72));
},
),
),
),
],
),
);
},
),
),
);
}
}
main.dart
import 'package:flutter/material.dart';
import '../widget/cell.dart';
/// This is on alternate entrypoint for this module to display Flutter UI in
/// a (multi-)view integration scenario.
// This is unfortunately in this file due to
// https://github.com/flutter/flutter/issues/72630.
@pragma("vm:entry-point")
void showCell() {
runApp(const Cell());
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Home'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xffA4D3EE),
body: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
),
);
}
}
Android Code
FlutterViewEngine.kt
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package com.example.android_mix_flutter
import android.app.Activity
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import io.flutter.embedding.android.ExclusiveAppComponent
import io.flutter.embedding.android.FlutterView
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.platform.PlatformPlugin
/**
* This is an application-specific wrapper class that exists to expose the intersection of an
* application's active activity and an application's visible view to a [FlutterEngine] for
* rendering.
*
* Omitted features from the [io.flutter.embedding.android.FlutterActivity] include:
* * **State restoration**. If you're integrating at the view level, you should handle activity
* state restoration yourself.
* * **Engine creations**. At this level of granularity, you must make an engine and attach.
* and all engine features like initial route etc must be configured on the engine yourself.
* * **Splash screens**. You must implement it yourself. Read from
* `addOnFirstFrameRenderedListener` as needed.
* * **Transparency, surface/texture**. These are just [FlutterView] level APIs. Set them on the
* [FlutterView] directly.
* * **Intents**. This doesn't do any translation of intents into actions in the [FlutterEngine].
* you must do them yourself.
* * **Back buttons**. You must decide whether to send it to Flutter via
* [FlutterEngine.getNavigationChannel.popRoute()], or consume it natively. Though that
* decision may be difficult due to https://github.com/flutter/flutter/issues/67011.
* * **Low memory signals**. You're strongly encouraged to pass the low memory signals (such
* as from the host `Activity`'s `onTrimMemory` callbacks) to the [FlutterEngine] to let
* Flutter and the Dart VM cull its own memory usage.
*
* Your own [FlutterView] integrating application may need a similar wrapper but you must decide on
* what the appropriate intersection between the [FlutterView], the [FlutterEngine] and your
* `Activity` should be for your own application.
*/
class FlutterViewEngine(val engine: FlutterEngine) : LifecycleObserver, ExclusiveAppComponent<Activity>{
private var flutterView: FlutterView? = null
private var activity: ComponentActivity? = null
private var platformPlugin: PlatformPlugin? = null
/**
* This is the intersection of an available activity and of a visible [FlutterView]. This is
* where Flutter would start rendering.
*/
private fun hookActivityAndView() {
// Assert state.
activity!!.let { activity ->
flutterView!!.let { flutterView ->
platformPlugin = PlatformPlugin(activity, engine.platformChannel)
engine.activityControlSurface.attachToActivity(this, activity.lifecycle)
flutterView.attachToFlutterEngine(engine)
activity.lifecycle.addObserver(this)
}
}
}
/**
* Lost the intersection of either an available activity or a visible
* [FlutterView].
*/
private fun unhookActivityAndView() {
// Stop reacting to activity events.
activity!!.lifecycle.removeObserver(this)
// Plugins are no longer attached to an activity.
engine.activityControlSurface.detachFromActivity()
// Release Flutter's control of UI such as system chrome.
platformPlugin!!.destroy()
platformPlugin = null
// Set Flutter's application state to detached.
engine.lifecycleChannel.appIsDetached();
// Detach rendering pipeline.
flutterView!!.detachFromFlutterEngine()
}
/**
* Signal that a host `Activity` is now ready. If there is no [FlutterView] instance currently
* attached to the view hierarchy and visible, Flutter is not yet rendering.
*
* You can also choose at this point whether to notify the plugins that an `Activity` is
* attached or not. You can also choose at this point whether to connect a Flutter
* [PlatformPlugin] at this point which allows your Dart program to trigger things like
* haptic feedback and read the clipboard. This sample arbitrarily chooses no for both.
*/
fun attachToActivity(activity: ComponentActivity) {
this.activity = activity
if (flutterView != null) {
hookActivityAndView()
}
}
/**
* Signal that a host `Activity` now no longer connected. If there were a [FlutterView] in
* the view hierarchy and visible at this moment, that [FlutterView] will stop rendering.
*
* You can also choose at this point whether to notify the plugins that an `Activity` is
* no longer attached or not. You can also choose at this point whether to disconnect Flutter's
* [PlatformPlugin] at this point which stops your Dart program being able to trigger things
* like haptic feedback and read the clipboard. This sample arbitrarily chooses yes for both.
*/
fun detachActivity() {
if (flutterView != null) {
unhookActivityAndView()
}
activity = null
}
/**
* Signal that a [FlutterView] instance is created and attached to a visible Android view
* hierarchy.
*
* If an `Activity` was also previously provided, this puts Flutter into the rendering state
* for this [FlutterView]. This also connects this wrapper class to listen to the `Activity`'s
* lifecycle to pause rendering when the activity is put into the background while the
* view is still attached to the view hierarchy.
*/
fun attachFlutterView(flutterView: FlutterView) {
this.flutterView = flutterView
if (activity != null) {
hookActivityAndView()
}
}
/**
* Signal that the attached [FlutterView] instance destroyed or no longer attached to a visible
* Android view hierarchy.
*
* If an `Activity` was attached, this stops Flutter from rendering. It also makes this wrapper
* class stop listening to the `Activity`'s lifecycle since it's no longer rendering.
*/
fun detachFlutterView() {
unhookActivityAndView()
flutterView = null
}
/**
* Callback to let Flutter respond to the `Activity`'s resumed lifecycle event while both an
* `Activity` and a [FlutterView] are attached.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private fun resumeActivity() {
if (activity != null) {
engine.lifecycleChannel.appIsResumed()
}
platformPlugin?.updateSystemUiOverlays()
}
/**
* Callback to let Flutter respond to the `Activity`'s paused lifecycle event while both an
* `Activity` and a [FlutterView] are attached.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private fun pauseActivity() {
if (activity != null) {
engine.lifecycleChannel.appIsInactive()
}
}
/**
* Callback to let Flutter respond to the `Activity`'s stopped lifecycle event while both an
* `Activity` and a [FlutterView] are attached.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun stopActivity() {
if (activity != null) {
engine.lifecycleChannel.appIsPaused()
}
}
// These events aren't used but would be needed for Flutter plugins consuming
// these events to function.
/**
* Pass through the `Activity`'s `onRequestPermissionsResult` signal to plugins that may be
* listening to it while the `Activity` and the [FlutterView] are connected.
*/
fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (activity != null && flutterView != null) {
engine
.activityControlSurface
.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
* Pass through the `Activity`'s `onActivityResult` signal to plugins that may be
* listening to it while the `Activity` and the [FlutterView] are connected.
*/
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (activity != null && flutterView != null) {
engine.activityControlSurface.onActivityResult(requestCode, resultCode, data);
}
}
/**
* Pass through the `Activity`'s `onUserLeaveHint` signal to plugins that may be
* listening to it while the `Activity` and the [FlutterView] are connected.
*/
fun onUserLeaveHint() {
if (activity != null && flutterView != null) {
engine.activityControlSurface.onUserLeaveHint();
}
}
/**
* Called when another App Component is about to become attached to the [ ] this App Component
* is currently attached to.
*
*
* This App Component's connections to the [io.flutter.embedding.engine.FlutterEngine]
* are still valid at the moment of this call.
*/
override fun detachFromFlutterEngine() {
// Do nothing here
}
/**
* Retrieve the App Component behind this exclusive App Component.
*
* @return The app component.
*/
override fun getAppComponent(): Activity {
return activity!!;
}
}
MListAdapter.kt
package com.example.android_mix_flutter// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.android_mix_flutter.databinding.AndroidCardBinding
import io.flutter.embedding.android.FlutterView
import io.flutter.plugin.common.MethodChannel
import java.util.*
import kotlin.random.Random
/**
* A demo-specific implementation of a [RecyclerView.Adapter] to setup the demo environment used
* to display view-level Flutter cells inside a list.
*
* The only instructional parts of this class are to show when to call
* [FlutterViewEngine.attachFlutterView] and [FlutterViewEngine.detachActivity] on a
* [FlutterViewEngine] equivalent class that you may want to create in your own application.
*/
class MListAdapter(context: Context, private val flutterViewEngine: FlutterViewEngine) : RecyclerView.Adapter<MListAdapter.Cell>() {
// Save the previous cells determined to be Flutter cells to avoid a confusing visual effect
// that the Flutter cells change position when scrolling back.
var previousFlutterCells = TreeSet<Int>();
private val matchParentLayout = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
private val random = Random.Default
private val flutterView = FlutterView(context)
private val flutterChannel = MethodChannel(flutterViewEngine.engine.dartExecutor, "dev.flutter.example/cell")
private var flutterCell: Cell? = null
/**
* A [RecyclerView.ViewHolder] based on the `android_card` layout XML.
*/
inner class Cell(val binding: AndroidCardBinding) : RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): Cell {
val binding = AndroidCardBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
// Let the default view holder have an "Android card" inflated from the layout XML. When
// needed, hide the Android card and show a Flutter one instead.
return Cell(binding)
}
override fun onBindViewHolder(cell: Cell, position: Int) {
// While scrolling forward, if no Flutter is presently showing, let the next one have a 1/3
// chance of being Flutter.
//
// While scrolling backward, let it be deterministic, and only show cells that were
// previously Flutter cells as Flutter cells.
if (previousFlutterCells.contains(position)
|| ((previousFlutterCells.isEmpty() || position > previousFlutterCells.last())
&& flutterCell == null
&& random.nextInt(3) == 0)) {
// If we're restoring a cell at a previous location, the current cell may not have
// recycled yet since that JVM timing is indeterministic. Yank it from the current one.
//
// This shouldn't produce any visual glitches since in the forward direction,
// Flutter cells were only introduced once the previous Flutter cell recycled.
if (flutterCell != null) {
Log.w("FeedAdapter", "While restoring a previous Flutter cell, a current "
+ "yet to be recycled Flutter cell was detached.")
flutterCell!!.binding.root.removeView(flutterView)
flutterViewEngine.detachFlutterView()
flutterCell = null
}
// Add the Flutter card and hide the Android card for the cells chosen to be Flutter
// cells.
cell.binding.root.addView(flutterView, matchParentLayout)
cell.binding.androidCard.visibility = View.GONE
// Keep track of the cell so we know which one to restore back to the "Android cell"
// state when the view gets recycled.
flutterCell = cell
// Keep track that this position has once been a Flutter cell. Let it be a Flutter cell
// again when scrolling back to this position.
previousFlutterCells.add(position)
// This is what makes the Flutter cell start rendering.
flutterViewEngine.attachFlutterView(flutterView)
// Tell Flutter which index it's at so Flutter could show the cell number too in its
// own widget tree.
flutterChannel.invokeMethod("setCellNumber", position)
} else {
// If it's not selected as a Flutter cell, just show the Android card.
cell.binding.androidCard.visibility = View.VISIBLE
cell.binding.cellNumber.text = position.toString();
}
}
override fun getItemCount() = 100
override fun onViewRecycled(cell: Cell) {
if (cell == flutterCell) {
cell.binding.root.removeView(flutterView)
flutterViewEngine.detachFlutterView()
flutterCell = null
}
super.onViewRecycled(cell)
}
}
MainActivity.kt
package com.example.android_mix_flutter// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.android_mix_flutter.databinding.ActivityMainBinding
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import java.util.*
// There are 3 files in this sample. com.example.android_mix_flutter.MainActivity and com.example.android_mix_flutter.ListAdapter are just
// fictional setups. FlutterViewEngine is instructional and demonstrates the
// various plumbing needed for a functioning FlutterView integration.
/**
* Main activity for this demo that shows a page with a `RecyclerView`.
*
* There are 3 files in this sample. com.example.android_mix_flutter.MainActivity and com.example.android_mix_flutter.ListAdapter are just fictional setups.
* FlutterViewEngine is instructional and demonstrates the various plumbing needed for a functioning
* FlutterView integration.
*/
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var flutterViewEngine: FlutterViewEngine
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// TODO: create a multi-engine version after
// https://github.com/flutter/flutter/issues/72009 is built.
val engine = FlutterEngine(applicationContext)
engine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"showCell"))
flutterViewEngine = FlutterViewEngine(engine)
// The activity and FlutterView have different lifecycles.
// Attach the activity right away but only start rendering when the
// view is also scrolled into the screen.
flutterViewEngine.attachToActivity(this)
val layoutManager = LinearLayoutManager(this)
val recyclerView = binding.recyclerView
val adapter = MListAdapter(this, flutterViewEngine)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
// If the activity was restarted, keep track of the previous scroll
// position and of the previous cell indices that were randomly selected
// as Flutter cells to preserve immersion.
layoutManager.onRestoreInstanceState(savedInstanceState?.getParcelable<Parcelable>("layoutManager"))
val previousFlutterCellsArray = savedInstanceState?.getIntegerArrayList("adapter")
if (previousFlutterCellsArray != null) {
adapter.previousFlutterCells = TreeSet(previousFlutterCellsArray)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable("layoutManager", binding.recyclerView.layoutManager?.onSaveInstanceState())
val previousFlutterCells = (binding.recyclerView.adapter as? MListAdapter)?.previousFlutterCells
if (previousFlutterCells != null) {
outState.putIntegerArrayList(
"adapter",
ArrayList(previousFlutterCells)
)
}
}
override fun onDestroy() {
super.onDestroy()
flutterViewEngine.detachActivity()
}
// These below aren't used here in this demo but would be needed for Flutter plugins that may
// consume these events.
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
flutterViewEngine.onRequestPermissionsResult(requestCode, permissions, grantResults)
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
flutterViewEngine.onActivityResult(requestCode, resultCode, data)
super.onActivityResult(requestCode, resultCode, data)
}
override fun onUserLeaveHint() {
flutterViewEngine.onUserLeaveHint()
super.onUserLeaveHint()
}
}
android_card.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="160dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/androidCard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="36dp"
android:layout_marginVertical="24dp"
app:cardBackgroundColor="#F1F1F1"
app:cardCornerRadius="16dp"
app:cardElevation="16dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/cellNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?attr/textAppearanceHeadline3"/>
</FrameLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Screenshots or Video
Process demonstration:
Exception information:
Logs
Logs
[Paste your logs here]Flutter Doctor output
[✓] Flutter (Channel stable, 3.13.2, on macOS 14.2.1 23C71 darwin-arm64, locale zh-Hans-CN)
• Flutter version 3.13.2 on channel stable at /xx/xx/Documents/flutter_profile/flutter_sdk/flutter_3.13.2
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision ff5b5b5fa6 (5 months ago), 2023-08-24 08:12:28 -0500
• Engine revision b20183e040
• Dart version 3.1.0
• DevTools version 2.25.0
• Pub download mirror https://pub.flutter-io.cn
• Flutter download mirror https://storage.flutter-io.cn
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
• Android SDK at /xx/xx/Library/Android/sdk
• Platform android-34, build-tools 34.0.0
• ANDROID_HOME = /xx/xx/Library/Android/sdk
• Java binary at: /Applications/Android Studio_2022.3.1.app/Contents/jbr/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 15.0)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 15A240d
• CocoaPods version 1.14.3
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2022.3)
• Android Studio at /Applications/Android Studio_2022.3.1.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
[✓] Android Studio (version 4.2)
• Android Studio at /Applications/Android Studio_2021.4.2.2.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6916264)
[✓] IntelliJ IDEA Ultimate Edition (version 2023.2.2)
• IntelliJ at /Applications/IntelliJ IDEA.app
• Flutter plugin version 75.1.4
• Dart plugin version 232.9559.10
[✓] VS Code (version 1.85.1)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.80.0
[✓] Connected device (3 available)
• sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 13 (API 33) (emulator)
• macOS (desktop) • macos • darwin-arm64 • macOS 14.2.1 23C71 darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome 120.0.6099.234
Metadata
Metadata
Assignees
Labels
a: existing-appsIntegration with existing apps via the add-to-app flowIntegration with existing apps via the add-to-app flowengineflutter/engine related. See also e: labels.flutter/engine related. See also e: labels.found in release: 3.16Found to occur in 3.16Found to occur in 3.16found in release: 3.19Found to occur in 3.19Found to occur in 3.19frameworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onplatform-androidAndroid applications specificallyAndroid applications specificallyr: fixedIssue is closed as already fixed in a newer versionIssue is closed as already fixed in a newer versionteam-androidOwned by Android platform teamOwned by Android platform team
