While all the answers produces the desired effects we should do some improvements here.
- First of all in most cases (speaking about auto scrolling) is useless using postFrameCallbacks because some stuff could be rendered after the ScrollController attachment (produced by the - attachmethod), the controller will scroll until the last position that he knows and that position could not be the latest in your view.
 
- Using - reverse:trueshould be a good trick to 'tail' the content but the physic will be reversed so when you try to manually move the scrollbar you must move it to the opposite side -> BAD UX.
 
- Using timers is a very bad practice when designing graphic interfaces -> timer are a kind of virus when used to update/spawn graphics artifacts. 
Anyway speaking about the question the right way to accomplish the task is using the jumpTo method with the hasClients method as a guard.
Whether any ScrollPosition objects have attached themselves to the ScrollController using the attach method.
If this is false, then members that interact with the ScrollPosition, such as position, offset, animateTo, and jumpTo, must not be called
Speaking in code simply do something like this:
if (_scrollController.hasClients) {
    _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
Anyway this code is still not enough, the method will be triggered even when the scrollable isn't at the end of the screen so if you are manually moving the bar the method will triggered and autoscrolling will be performed.
We ca do better, with the help of a listener and a couple of bool will be fine.
I'm using this technique to visualize in a SelectableText the value of a CircularBuffer of size 100000 and the content keeps updating correctly, the autoscroll is very smooth and there are not performance issues even for very very very long contents. Maybe as someone said in other answers the animateTo method could be smoother and more customizable so feel free to give a try.
- First of all declare these variables:
ScrollController _scrollController = new ScrollController();
bool _firstAutoscrollExecuted = false;
bool _shouldAutoscroll = false;
- Then let's create a method for autoscrolling:
void _scrollToBottom() {
    _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
- Then we need the listener:
void _scrollListener() {
    _firstAutoscrollExecuted = true;
    if (_scrollController.hasClients && _scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
        _shouldAutoscroll = true;
    } else {
        _shouldAutoscroll = false;
    }
}
- Register it in initState:
@override
void initState() {
    super.initState();
    _scrollController.addListener(_scrollListener);
}
- Remove the listener in your dispose:
@override
void dispose() {
    _scrollController.removeListener(_scrollListener);
    super.dispose();
}
- Then trigger _scrollToBottom, basing on your logic and needs, in yoursetState:
setState(() {
    if (_scrollController.hasClients && _shouldAutoscroll) {
        _scrollToBottom();
    }
    if (!_firstAutoscrollExecuted && _scrollController.hasClients) {
         _scrollToBottom();
    }
});
EXPLANATION
- We made a simple method: _scrollToBottom()in order to avoid code repetitions;
- We made a _scrollListener()and we attached it to the_scrollControllerin theinitState-> will be triggered after the first time that the scrollbar will move. In this listener we update the value of the bool value_shouldAutoscrollin order to understand if the scrollbar is at the bottom of the screen.
- We removed the listener in the disposejust to be sure to not do useless stuff after the widget dispose.
- In our setStatewhen we are sure that the_scrollControlleris attached and that's at the bottom (checking for the value ofshouldAutoscroll) we can call_scrollToBottom().
 At the same time, only for the 1st execution we force the_scrollToBottom()short-circuiting on the value of_firstAutoscrollExecuted.