“未处理的异常:调用onTap()时出现NoSuchMethodError”

问题描述 投票:0回答:1

我不熟悉Flutter,最近开始从事我的第一个Flutter项目。

其想法是,当用户触摸按钮/图像时,它应该播放声音。

我正在使用android studio创建该应用。我已经导入了一个名为Audioplayers以帮助播放音频。

[当我运行代码时,会构建该应用程序并显示图像,但是当我点击图像时,它给了我一个我无法解决的错误:

Unhandled Exception: NoSuchMethodError: The getter 'position' was called on null

这是代码:

MaterialApp(
   home: Scaffold(
     backgroundColor: Colors.black,
     body: Center(
       child: GestureDetector(
         onTap: () {
           AudioPlayer audioPlayer = AudioPlayer();
           audioPlayer.setVolume(1.0);

           playLocal() async {
             int result = await audioPlayer.play(
                 'C:/Users/JaganathPrathap/AndroidStudioProjects/bruh/assetsproj/audio/movie_1.mp3',
                 isLocal: true);
           }
         },
         child: Image(
           image: AssetImage('assetsproj/images/PngItem_4931119.png'),
         ),
       ),
     ),
   ),
 ),

这是错误:

E/flutter (11532): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The getter 'position' was called on null.
E/flutter (11532): Receiver: null
E/flutter (11532): Tried calling: position
E/flutter (11532): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
E/flutter (11532): #1      TapGestureRecognizer.handleTapDown (package:flutter/src/gestures/tap.dart:456:28)
E/flutter (11532): #2      BaseTapGestureRecognizer._checkDown (package:flutter/src/gestures/tap.dart:256:5)
E/flutter (11532): #3      BaseTapGestureRecognizer.didExceedDeadline (package:flutter/src/gestures/tap.dart:227:5)
E/flutter (11532): #4      PrimaryPointerGestureRecognizer.didExceedDeadlineWithEvent (package:flutter/src/gestures/recognizer.dart:493:5)
E/flutter (11532): #5      PrimaryPointerGestureRecognizer.addAllowedPointer.<anonymous closure> (package:flutter/src/gestures/recognizer.dart:446:40)
E/flutter (11532): #6      _rootRun (dart:async/zone.dart:1122:38)
E/flutter (11532): #7      _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (11532): #8      _CustomZone.runGuarded (dart:async/zone.dart:925:7)
E/flutter (11532): #9      _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:965:23)
E/flutter (11532): #10     _rootRun (dart:async/zone.dart:1126:13)
E/flutter (11532): #11     _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (11532): #12     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:949:23)
E/flutter (11532): #13     Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:23:15)
E/flutter (11532): #14     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:384:19)
E/flutter (11532): #15     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:418:5)
E/flutter (11532): #16     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174:12)
E/flutter (11532): 

我不确定为什么会收到此错误。

flutter dart nosuchmethoderror
1个回答
0
投票

我不确定为什么会收到此错误。

您的代码缺少Flutter应用正常运行所需的一切,因此可能是原因。

您需要了解的三件事:

  1. 小部件
  2. 状态管理
  3. 合成和渲染

没有牢牢掌握这些概念,您就无法编写Flutter应用程序。

[对不起,我听起来像个万事通,但当您显示的代码有很多缺失的片段时,这个问题无法回答,表明您对其中的任何一个都不了解上述三点。

如果您阅读了软件包的Readme File,则会看到以下一行:

此示例捆绑了一个Player Widget,可以将其用作非常简单的音频播放器界面。

看看这个,并在上面的句子中标记“非常简单”部分。记住,这只是一个小部件(应用程序的一个组件)的一个示例,该小部件可以包含在您自己的代码中,用于管理小部件,状态和其他所有内容(代码中完全缺少的内容)。

这里是非常简单示例小部件的代码:


// player_widget.dart

import 'dart:async';

import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

enum PlayerState { stopped, playing, paused }
enum PlayingRouteState { speakers, earpiece }

class PlayerWidget extends StatefulWidget {
  final String url;
  final PlayerMode mode;

  PlayerWidget({@required this.url, this.mode = PlayerMode.MEDIA_PLAYER});

  @override
  State<StatefulWidget> createState() {
    return _PlayerWidgetState(url, mode);
  }
}

class _PlayerWidgetState extends State<PlayerWidget> {
  String url;
  PlayerMode mode;

  AudioPlayer _audioPlayer;
  AudioPlayerState _audioPlayerState;
  Duration _duration;
  Duration _position;

  PlayerState _playerState = PlayerState.stopped;
  PlayingRouteState _playingRouteState = PlayingRouteState.speakers;
  StreamSubscription _durationSubscription;
  StreamSubscription _positionSubscription;
  StreamSubscription _playerCompleteSubscription;
  StreamSubscription _playerErrorSubscription;
  StreamSubscription _playerStateSubscription;

  get _isPlaying => _playerState == PlayerState.playing;
  get _isPaused => _playerState == PlayerState.paused;
  get _durationText => _duration?.toString()?.split('.')?.first ?? '';
  get _positionText => _position?.toString()?.split('.')?.first ?? '';

  get _isPlayingThroughEarpiece =>
      _playingRouteState == PlayingRouteState.earpiece;

  _PlayerWidgetState(this.url, this.mode);

  @override
  void initState() {
    super.initState();
    _initAudioPlayer();
  }

  @override
  void dispose() {
    _audioPlayer.stop();
    _durationSubscription?.cancel();
    _positionSubscription?.cancel();
    _playerCompleteSubscription?.cancel();
    _playerErrorSubscription?.cancel();
    _playerStateSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            IconButton(
                onPressed: _isPlaying ? null : () => _play(),
                iconSize: 64.0,
                icon: Icon(Icons.play_arrow),
                color: Colors.cyan),
            IconButton(
                onPressed: _isPlaying ? () => _pause() : null,
                iconSize: 64.0,
                icon: Icon(Icons.pause),
                color: Colors.cyan),
            IconButton(
                onPressed: _isPlaying || _isPaused ? () => _stop() : null,
                iconSize: 64.0,
                icon: Icon(Icons.stop),
                color: Colors.cyan),
            IconButton(
                onPressed: _earpieceOrSpeakersToggle,
                iconSize: 64.0,
                icon: _isPlayingThroughEarpiece
                    ? Icon(Icons.volume_up)
                    : Icon(Icons.hearing)
                ,
                color:  Colors.cyan),
          ],
        ),
        Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Padding(
              padding: EdgeInsets.all(12.0),
              child: Stack(
                children: [
                  Slider(
                    onChanged: (v) {
                      final Position = v * _duration.inMilliseconds;
                      _audioPlayer
                          .seek(Duration(milliseconds: Position.round()));
                    },
                    value: (_position != null &&
                            _duration != null &&
                            _position.inMilliseconds > 0 &&
                            _position.inMilliseconds < _duration.inMilliseconds)
                        ? _position.inMilliseconds / _duration.inMilliseconds
                        : 0.0,
                  ),
                ],
              ),
            ),
            Text(
              _position != null
                  ? '${_positionText ?? ''} / ${_durationText ?? ''}'
                  : _duration != null ? _durationText : '',
              style: TextStyle(fontSize: 24.0),
            ),
          ],
        ),
        Text("State: $_audioPlayerState")
      ],
    );
  }

  void _initAudioPlayer() {
    _audioPlayer = AudioPlayer(mode: mode);

    _durationSubscription = _audioPlayer.onDurationChanged.listen((duration) {
      setState(() => _duration = duration);

      // TODO implemented for iOS, waiting for android impl
      if (Theme.of(context).platform == TargetPlatform.iOS) {
        // (Optional) listen for notification updates in the background
        _audioPlayer.startHeadlessService();

        // set at least title to see the notification bar on ios.
        _audioPlayer.setNotification(
            title: 'App Name',
            artist: 'Artist or blank',
            albumTitle: 'Name or blank',
            imageUrl: 'url or blank',
            forwardSkipInterval: const Duration(seconds: 30), // default is 30s
            backwardSkipInterval: const Duration(seconds: 30), // default is 30s
            duration: duration,
            elapsedTime: Duration(seconds: 0));
      }
    });

    _positionSubscription =
        _audioPlayer.onAudioPositionChanged.listen((p) => setState(() {
              _position = p;
            }));

    _playerCompleteSubscription =
        _audioPlayer.onPlayerCompletion.listen((event) {
      _onComplete();
      setState(() {
        _position = _duration;
      });
    });

    _playerErrorSubscription = _audioPlayer.onPlayerError.listen((msg) {
      print('audioPlayer error : $msg');
      setState(() {
        _playerState = PlayerState.stopped;
        _duration = Duration(seconds: 0);
        _position = Duration(seconds: 0);
      });
    });

    _audioPlayer.onPlayerStateChanged.listen((state) {
      if (!mounted) return;
      setState(() {
        _audioPlayerState = state;
      });
    });

    _audioPlayer.onNotificationPlayerStateChanged.listen((state) {
      if (!mounted) return;
      setState(() => _audioPlayerState = state);
    });

    _playingRouteState = PlayingRouteState.speakers;
  }

  Future<int> _play() async {
    final playPosition = (_position != null &&
            _duration != null &&
            _position.inMilliseconds > 0 &&
            _position.inMilliseconds < _duration.inMilliseconds)
        ? _position
        : null;
    final result = await _audioPlayer.play(url, position: playPosition);
    if (result == 1) setState(() => _playerState = PlayerState.playing);

    // default playback rate is 1.0
    // this should be called after _audioPlayer.play() or _audioPlayer.resume()
    // this can also be called everytime the user wants to change playback rate in the UI
    _audioPlayer.setPlaybackRate(playbackRate: 1.0);

    return result;
  }

  Future<int> _pause() async {
    final result = await _audioPlayer.pause();
    if (result == 1) setState(() => _playerState = PlayerState.paused);
    return result;
  }

  Future<int> _earpieceOrSpeakersToggle() async {
    final result = await _audioPlayer.earpieceOrSpeakersToggle();
    if (result == 1)
      setState(() => _playingRouteState =
          _playingRouteState == PlayingRouteState.speakers
              ? PlayingRouteState.earpiece
              : PlayingRouteState.speakers);
    return result;
  }

  Future<int> _stop() async {
    final result = await _audioPlayer.stop();
    if (result == 1) {
      setState(() {
        _playerState = PlayerState.stopped;
        _position = Duration();
      });
    }
    return result;
  }

  void _onComplete() {
    setState(() => _playerState = PlayerState.stopped);
  }
}

现在,所缺少的只是组成Flutter应用程序的其余部分。

[如果您浏览此main.dart文件中的代码,并将其与代码中包含的内容进行比较,您可能会发现一些缺少的部分:

// main.dart

import 'dart:async';
import 'dart:io';

import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:flutter/src/foundation/constants.dart';

import 'player_widget.dart';

typedef void OnError(Exception exception);

const kUrl1 = 'https://luan.xyz/files/audio/ambient_c_motion.mp3';
const kUrl2 = 'https://luan.xyz/files/audio/nasa_on_a_mission.mp3';
const kUrl3 = 'http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio1xtra_mf_p';

void main() {
  runApp(MaterialApp(home: ExampleApp()));
}

class ExampleApp extends StatefulWidget {
  @override
  _ExampleAppState createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  AudioCache audioCache = AudioCache();
  AudioPlayer advancedPlayer = AudioPlayer();
  String localFilePath;

  @override
  void initState() {
    super.initState();

    if (kIsWeb) {
      // Calls to Platform.isIOS fails on web
      return;
    }
    if (Platform.isIOS) {
      if (audioCache.fixedPlayer != null) {
        audioCache.fixedPlayer.startHeadlessService();
      }
      advancedPlayer.startHeadlessService();
    }
  }

  Future _loadFile() async {
    final bytes = await readBytes(kUrl1);
    final dir = await getApplicationDocumentsDirectory();
    final file = File('${dir.path}/audio.mp3');

    await file.writeAsBytes(bytes);
    if (await file.exists()) {
      setState(() {
        localFilePath = file.path;
      });
    }
  }

  Widget remoteUrl() {
    return SingleChildScrollView(
      child: _Tab(children: [
        Text(
          'Sample 1 ($kUrl1)',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        PlayerWidget(url: kUrl1),
        Text(
          'Sample 2 ($kUrl2)',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        PlayerWidget(url: kUrl2),
        Text(
          'Sample 3 ($kUrl3)',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        PlayerWidget(url: kUrl3),
        Text(
          'Sample 4 (Low Latency mode) ($kUrl1)',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        PlayerWidget(url: kUrl1, mode: PlayerMode.LOW_LATENCY),
      ]),
    );
  }

  Widget localFile() {
    return _Tab(children: [
      Text('File: $kUrl1'),
      _Btn(txt: 'Download File to your Device', onPressed: () => _loadFile()),
      Text('Current local file path: $localFilePath'),
      localFilePath == null
          ? Container()
          : PlayerWidget(
              url: localFilePath,
            ),
    ]);
  }

  Widget localAsset() {
    return SingleChildScrollView(
      child: _Tab(children: [
        Text('Play Local Asset \'audio.mp3\':'),
        _Btn(txt: 'Play', onPressed: () => audioCache.play('audio.mp3')),
        Text('Loop Local Asset \'audio.mp3\':'),
        _Btn(txt: 'Loop', onPressed: () => audioCache.loop('audio.mp3')),
        Text('Play Local Asset \'audio2.mp3\':'),
        _Btn(txt: 'Play', onPressed: () => audioCache.play('audio2.mp3')),
        Text('Play Local Asset In Low Latency \'audio.mp3\':'),
        _Btn(
            txt: 'Play',
            onPressed: () =>
                audioCache.play('audio.mp3', mode: PlayerMode.LOW_LATENCY)),
        Text('Play Local Asset Concurrently In Low Latency \'audio.mp3\':'),
        _Btn(
            txt: 'Play',
            onPressed: () async {
              await audioCache.play('audio.mp3', mode: PlayerMode.LOW_LATENCY);
              await audioCache.play('audio2.mp3', mode: PlayerMode.LOW_LATENCY);
            }),
        Text('Play Local Asset In Low Latency \'audio2.mp3\':'),
        _Btn(
            txt: 'Play',
            onPressed: () =>
                audioCache.play('audio2.mp3', mode: PlayerMode.LOW_LATENCY)),
        getLocalFileDuration(),
      ]),
    );
  }

  Future<int> _getDuration() async {
    File audiofile = await audioCache.load('audio2.mp3');
    await advancedPlayer.setUrl(
      audiofile.path,
    );
    int duration = await Future.delayed(
        Duration(seconds: 2), () => advancedPlayer.getDuration());
    return duration;
  }

  getLocalFileDuration() {
    return FutureBuilder<int>(
      future: _getDuration(),
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
            return Text('No Connection...');
          case ConnectionState.active:
          case ConnectionState.waiting:
            return Text('Awaiting result...');
          case ConnectionState.done:
            if (snapshot.hasError) return Text('Error: ${snapshot.error}');
            return Text(
                'audio2.mp3 duration is: ${Duration(milliseconds: snapshot.data)}');
        }
        return null; // unreachable
      },
    );
  }

  Widget notification() {
    return _Tab(children: [
      Text('Play notification sound: \'messenger.mp3\':'),
      _Btn(
          txt: 'Play',
          onPressed: () =>
              audioCache.play('messenger.mp3', isNotification: true)),
    ]);
  }

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        StreamProvider<Duration>.value(
            initialData: Duration(),
            value: advancedPlayer.onAudioPositionChanged),
      ],
      child: DefaultTabController(
        length: 5,
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabs: [
                Tab(text: 'Remote Url'),
                Tab(text: 'Local File'),
                Tab(text: 'Local Asset'),
                Tab(text: 'Notification'),
                Tab(text: 'Advanced'),
              ],
            ),
            title: Text('audioplayers Example'),
          ),
          body: TabBarView(
            children: [
              remoteUrl(),
              localFile(),
              localAsset(),
              notification(),
              Advanced(
                advancedPlayer: advancedPlayer,
              )
            ],
          ),
        ),
      ),
    );
  }
}

class Advanced extends StatefulWidget {
  final AudioPlayer advancedPlayer;

  const Advanced({Key key, this.advancedPlayer}) : super(key: key);

  @override
  _AdvancedState createState() => _AdvancedState();
}

class _AdvancedState extends State<Advanced> {
  bool seekDone;

  @override
  void initState() {
    widget.advancedPlayer.seekCompleteHandler =
        (finished) => setState(() => seekDone = finished);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    final audioPosition = Provider.of<Duration>(context);
    return SingleChildScrollView(
      child: _Tab(
        children: [
          Column(children: [
            Text('Source Url'),
            Row(children: [
              _Btn(
                  txt: 'Audio 1',
                  onPressed: () => widget.advancedPlayer.setUrl(kUrl1)),
              _Btn(
                  txt: 'Audio 2',
                  onPressed: () => widget.advancedPlayer.setUrl(kUrl2)),
              _Btn(
                  txt: 'Stream',
                  onPressed: () => widget.advancedPlayer.setUrl(kUrl3)),
            ], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
          ]),
          Column(children: [
            Text('Release Mode'),
            Row(children: [
              _Btn(
                  txt: 'STOP',
                  onPressed: () =>
                      widget.advancedPlayer.setReleaseMode(ReleaseMode.STOP)),
              _Btn(
                  txt: 'LOOP',
                  onPressed: () =>
                      widget.advancedPlayer.setReleaseMode(ReleaseMode.LOOP)),
              _Btn(
                  txt: 'RELEASE',
                  onPressed: () => widget.advancedPlayer
                      .setReleaseMode(ReleaseMode.RELEASE)),
            ], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
          ]),
          Column(children: [
            Text('Volume'),
            Row(children: [
              _Btn(
                  txt: '0.0',
                  onPressed: () => widget.advancedPlayer.setVolume(0.0)),
              _Btn(
                  txt: '0.5',
                  onPressed: () => widget.advancedPlayer.setVolume(0.5)),
              _Btn(
                  txt: '1.0',
                  onPressed: () => widget.advancedPlayer.setVolume(1.0)),
              _Btn(
                  txt: '2.0',
                  onPressed: () => widget.advancedPlayer.setVolume(2.0)),
            ], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
          ]),
          Column(children: [
            Text('Control'),
            Row(children: [
              _Btn(
                  txt: 'resume',
                  onPressed: () => widget.advancedPlayer.resume()),
              _Btn(
                  txt: 'pause', onPressed: () => widget.advancedPlayer.pause()),
              _Btn(txt: 'stop', onPressed: () => widget.advancedPlayer.stop()),
              _Btn(
                  txt: 'release',
                  onPressed: () => widget.advancedPlayer.release()),
            ], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
          ]),
          Column(children: [
            Text('Seek in milliseconds'),
            Row(children: [
              _Btn(
                  txt: '100ms',
                  onPressed: () {
                    widget.advancedPlayer.seek(Duration(
                        milliseconds: audioPosition.inMilliseconds + 100));
                    setState(() => seekDone = false);
                  }),
              _Btn(
                  txt: '500ms',
                  onPressed: () {
                    widget.advancedPlayer.seek(Duration(
                        milliseconds: audioPosition.inMilliseconds + 500));
                    setState(() => seekDone = false);
                  }),
              _Btn(
                  txt: '1s',
                  onPressed: () {
                    widget.advancedPlayer
                        .seek(Duration(seconds: audioPosition.inSeconds + 1));
                    setState(() => seekDone = false);
                  }),
              _Btn(
                  txt: '1.5s',
                  onPressed: () {
                    widget.advancedPlayer.seek(Duration(
                        milliseconds: audioPosition.inMilliseconds + 1500));
                    setState(() => seekDone = false);
                  }),
            ], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
          ]),
          Column(children: [
            Text('Rate'),
            Row(children: [
              _Btn(
                  txt: '0.5',
                  onPressed: () =>
                      widget.advancedPlayer.setPlaybackRate(playbackRate: 0.5)),
              _Btn(
                  txt: '1.0',
                  onPressed: () =>
                      widget.advancedPlayer.setPlaybackRate(playbackRate: 1.0)),
              _Btn(
                  txt: '1.5',
                  onPressed: () =>
                      widget.advancedPlayer.setPlaybackRate(playbackRate: 1.5)),
              _Btn(
                  txt: '2.0',
                  onPressed: () =>
                      widget.advancedPlayer.setPlaybackRate(playbackRate: 2.0)),
            ], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
          ]),
          Text('Audio Position: ${audioPosition}'),
          seekDone == null
              ? SizedBox(
                  width: 0,
                  height: 0,
                )
              : Text(seekDone ? "Seek Done" : "Seeking..."),
        ],
      ),
    );
  }
}

class _Tab extends StatelessWidget {
  final List<Widget> children;

  const _Tab({Key key, this.children}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        alignment: Alignment.topCenter,
        padding: EdgeInsets.all(16.0),
        child: SingleChildScrollView(
          child: Column(
            children: children
                .map((w) => Container(child: w, padding: EdgeInsets.all(6.0)))
                .toList(),
          ),
        ),
      ),
    );
  }
}

class _Btn extends StatelessWidget {
  final String txt;
  final VoidCallback onPressed;

  const _Btn({Key key, this.txt, this.onPressed}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ButtonTheme(
        minWidth: 48.0,
        child: RaisedButton(child: Text(txt), onPressed: onPressed));
  }
}

© www.soinside.com 2019 - 2024. All rights reserved.